C# 泛型详解(泛型类,方法,接口,委托,约束,反射 )

2023-11-17

目录

一、什么是泛型

二、为什么要用泛型

三、泛型和Object类型的区别

四、泛型类

五、泛型方法

六、泛型接口

七、泛型委托

八、泛型约束

九、泛型配合反射

结束


一、什么是泛型

先看一段介绍

泛型(Generic),是将不确定的类型预先定义下来的一种C#高级语法,我们在使用一个类,接口或者方法前,不知道用户将来传什么类型,或者我们写的类,接口或方法相同的代码可以服务不同的类型,就可以定义为泛型。这会大大简化我们的代码结构,同时让后期维护变得容易。

泛型很适用于集合,我们常见的泛型集合有:List<T>,Dictionary<K,V>等等(T,K,V就代表不确定的类型,它是一种类型占位符),无一不是利用的泛型这一特性,若没有泛型,我们会多出很多重载方法,以解决类型不同,但是执行逻辑相同的情况。

看看上面这一堆介绍看着都想睡觉了,没办法,CSDN要求文字个数不够就不给推荐,估计这些简介也没几个人会去仔细看的哈,哈哈哈。

废话一大堆,那么泛型到底长啥样?,看到下面代码中的 “T” 没有,那就是泛型。

namespace 泛型
{
    class Program
    {
        static void Main(string[] args)
        {
            Test<string>();

            Console.ReadKey();
        }

        static void Test<T>()
        {
            Console.WriteLine(typeof(T).FullName);
        }
    }
}

输出:System.String

你可能会问,这个 “T” 就是泛型啊?把 “T” 改成鸡,那鸡不也是泛型了?

namespace 泛型
{
    class Program
    {
        static void Main(string[] args)
        {
            Test<string>();
            Console.ReadKey();
        }

        static void Test<鸡>()
        {
            Console.WriteLine("鸡你太美");
        }
    }
}

是的,没错,输出:鸡你太美

二、为什么要用泛型

先看一个例子

namespace 泛型
{
    class Program
    {
        static void Main(string[] args)
        {
            int result = Test.Add(3, 4);
            Console.WriteLine(result);

            Console.ReadKey();
        }

    }

    public class Test
    {
        public static int Add(int x, int y)
        {
            return x + y;
        }

        public static int Add(string x, string y)
        {
            return int.Parse(x) + int.Parse(y);
        }
    }
}

比如,我们需要封装一个加法的算法,但是传入的类型,可能有 int 类型,也可能有 string 类型,还可能有其他的类型,但是每多一种类型,你就要多加一个方法,非常的麻烦,臃肿。

那么有没有方法来解决这个问题呢,于是后面C#2.0中,就有了泛型这个概念,它并不是语法糖,看字面意思像 “泛滥的类型” 的意思。

那么下面就用泛型的方式来封装一下这个加法运算。

注意一下写法,在 Add 后面要加上 <T> 这句,不然会报错。

正确方式

下面是封装加法的完整代码

public class Test
{
    public static int Add<T>(T x,T y)
    {
        try
        {
            return int.Parse(x.ToString()) + int.Parse(y.ToString());
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        return -1;
    }
}

测试第一种

class Program
{
    static void Main(string[] args)
    {
        int result = Test.Add("3", "4");
        Console.WriteLine(result);

        Console.ReadKey();
    }
}

输出:7

测试第二种

class Program
{
    static void Main(string[] args)
    {
        int result = Test.Add(4, 5);
        Console.WriteLine(result);

        Console.ReadKey();
    }
}

输出:9

三、泛型和Object类型的区别

看了上面的案例,你会觉得,泛型是不是 Object 类型不是也差不多?下面是一些介绍:

C# 中 Object 是一切类型的基类,可以用来表示所有类型,而泛型是指将类型参数化以达到代码复用提高软件开发工作效率的一种数据类型。

你可以将泛型理解成替换,在使用的时候将泛型参数替换成具体的类型,这个过程是在编译的时候进行的,使用泛型编译器依然能够检测出类型错误。泛型不用拆箱装箱。

而 Object 表示其他类型是通过类型转换来完成的,而所有类型转化为 Object 类型都是合法的,所以即使你先将 Object 对象赋值为一个整数再赋值为一个字符串,编译器都认为是合法的。

Object 类型

> 优点:
> 1. object类型可以用来引用任何类型的实例;
> 2. object类型可以存储任何类型的值;
> 3. 可以定义object类型的参数;
> 4. 可以把object作为返回类型。

> 缺点:
> 1. 会因为程序员没有记住使用的类型而出错,造成类型不兼容;
> 2. 值类型和引用类型的互化即装箱拆箱使系统性能下降。

泛型,Object 类型,var 类型,这三种类型,看着很类似,但其实泛型的作用更不止如此,还有泛型类,泛型方法,泛型接口,泛型约束等,下面分别来介绍这几个功能

四、泛型类

泛型类是指这个类的某些字段的类型是不确定的,只有在构造的时候才能确定下的类,泛型类一般在数据结构中用的比较多,比如 C# 自带的 List<T>,Dictionary<TKey, TValue> 等,我也写过自定义 List 相关的帖子,有兴趣的可以去看下

C# 自定义List_熊思宇的博客-CSDN博客_c#定义list

1.泛型类案例

下面看一个例子

namespace 泛型
{
    class Program
    {
        static void Main(string[] args)
        {
            Test<int> test = new Test<int>();
            test.Add(1);
            test.Add(3);
            Console.WriteLine(test.Count);

            Console.ReadKey();
        }
    }

    public class Test<T> 
    {
        private List<T> list = new List<T>();

        public int Count => list.Count;

        public void Add(T t)
        {
            if(t != null)
                list.Add(t);
        }
    }
}

运行后输出:2

Test 类此时并没有添加任何的约束,这个是不推荐的,至少应该加一个,如下

public class Test<T> where T : new()
{
    private List<T> list = new List<T>();

    public int Count => list.Count;

    public void Add(T t)
    {
        if(t != null)
            list.Add(t);
    }
}

关于泛型约束,在后面的章节中会有详细的介绍。

2.泛型类继承

泛型类型继承写法:

public class Test1<T>
{
  
}

public class Test2<T> : Test1<T>
{

}

指定基类的类型写法:

public class Test1<T>
{
  
}

public class Test2<T> : Test1<int>
{

}

关于泛型类的继承,后面我再单独写文章进行介绍吧

五、泛型方法

泛型方法是指通过泛型来约束方法中的参数类型,如果没有泛型,每次方法中的参数类型都是固定的,不能随意更改,在使用泛型后,方法中的数据类型则有指定的泛型来约束,即可以根据提供的泛型来传递不同类型的参数。

泛型方法的返回类型和参数类型都可以使用泛型。如下:

public static T GetT<T>(T a)
{
    return a;
}

泛型方法同样可以使用泛型约束,只是泛型方法中的 T 只能用在方法内部和返回值,而泛型类中的 T 可以应用于整个类的字段和方法。

public class Test
{
    public void Add<T>(T t) where T : new()
    {
        List<T> list = new List<T>();
        if (t != null) list.Add(t);
    }
}

如果要对 T 类型作一些操作,则需要用到反射。

六、泛型接口

泛型接口如下

public interface IFace<T>  
{  
    void SayHi(T msg);  
}  

同样的,泛型接口也可以使用泛型约束

public interface IFace<T> where T : new() 
{  
    void SayHi(T msg);  
}  

实现泛型接口有两种情况

/// <summary>  
/// 1.普通类实现泛型接口  
/// </summary>  
public class MyClass1 : IFace<string>  
{  
    public void SayHi(string msg)  
    {  
        Console.WriteLine(msg);  
    }  
}  

/// <summary>  
/// 2.泛型类继承泛型接口  
/// </summary>  
/// <typeparam name="T"></typeparam>  
public class MyClass2<T> : IFace<T>  
{  
    public void SayHi(T msg)  
    {  
        Console.WriteLine(msg);  
    }  
} 

案例

namespace 泛型
{
    class Program
    {
        static void Main(string[] args)
        {
            IFace<Msg> face = new Test();
            face.SayHi(new Msg());
            Console.ReadKey();
        }
    }

    public interface IFace<T> where T : new()
    {
        void SayHi(T msg);
    }

    public class Test : IFace<Msg>
    {
        public void SayHi(Msg msg)
        {
            Console.WriteLine(msg.MyName);
        }
    }

    public class Msg
    {
        public string MyName = "张三";
    }
}

输出:张三

七、泛型委托

泛型委托和普通委托差别不大,只是参数由具体类型变成了 "T"

public delegate void MyDelegate<T>(T args); 

案例

namespace 泛型
{
    class Program
    {
        delegate void MyDelegate<T>(T args);

        static void Main(string[] args)
        {
            MyDelegate<string> myDelegate = SayHi;
            myDelegate("哈喽");

            Console.ReadKey();
        }

        static void SayHi(string msg)
        {
            Console.WriteLine(msg);
        }
    }
}

输出:哈喽

八、泛型约束

泛型约束就是对 “T” 类型的数据做一定的限制,防止在运用过程中,做一些违规的操作,来保证数据的安全。

六种类型的约束:

T:结构

类型参数必须是值类型。可以指定除 Nullable 以外的任何值类型。有关更多信息,请参见使用可空类型(C# 编程指南)。

T:类

类型参数必须是引用类型,包括任何类、接口、委托或数组类型。

T:new()

类型参数必须具有无参数的公共构造函数。当与其他约束一起使用时,new() 约束必须最后指定。

T:<基类名>

类型参数必须是指定的基类或派生自指定的基类。

T:<接口名称>

类型参数必须是指定的接口或实现指定的接口。可以指定多个接口约束。约束接口也可以是泛型的。

T:U

为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。这称为裸类型约束。

案例

namespace 泛型
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.ReadKey();
        }
    }

    /// <summary>  
    /// 泛型接口  
    /// </summary>  
    /// <typeparam name="T"></typeparam>  
    public interface IFace<T>
    {
        void SayHi(T msg);
    } 

    /// <summary>  
    /// 泛型约束  
    /// </summary>  
    public class MyClass1<T, K, V, W, X,Y>
        where T : struct        //约束 T 必须是值类型  
        where K : class         //约束 K 必须是引用类型   
        where V : IFace<T>      //约束 V 必须实现 IFace 接口  
        where W : K             //约束 W 必须是 K 类型,或者是 K 类型的子类  
        where X : class, new()  //约束 X 必须是引用类型,并且有一个无参数的构造函数,当有多个约束时,new()必须写在最后  
        where Y : MyClass2      //约束 Y 必须是 MyClass2 类型,或者继承于 MyClass2 类
    {
        public void Add(T num)
        {
            Console.WriteLine(num);
        }
    }

    public class MyClass2
    {

    }
}

这个案例,几乎介绍了所有的泛型约束的使用方法,有兴趣的可以亲自动手写一写。

九、泛型配合反射

现在有一个需求,用户修改个人资料,在提交这里必须要判断是否有修改内容,如果没有修改,点击提交按钮则不提交,那要怎么判断呢?

有人可能会说,用 if...else 去判断就好了,可以这么写没错,但资料如果有几百个地方改了,不会还用 if...else 吧,那整篇代码全是 if...else 了,就像我同事开玩笑一样:“我不会高数,我只会写 if...else...”

下面这个案例就教你如何来解决这个问题。

代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace 泛型
{
    class Program
    {
        static void Main(string[] args)
        {
            Test test1 = new Test();
            test1.Names = "张三";
            test1.Age = 24;
            test1.Height = 179.9f;
            Test test2 = new Test();
            test2.Names = "李四";
            test2.Age = 31;
            test2.Height = 163.4f;

            bool result = CompareType(test1, test2);
            Console.WriteLine("是否相同:" + result);

            Console.ReadKey();
        }

        /// <summary>
        /// 比较--两个类型一样的实体类对象的值
        /// </summary>
        /// <param name="oneT"></param>
        /// <returns>返回true表示两个对象的数据相同,返回false表示不相同</returns>
        public static bool CompareType<T>(T oneT, T twoT)
        {
            bool result = true;//两个类型作比较时使用,如果有不一样的就false
            Type typeOne = oneT.GetType();
            Type typeTwo = twoT.GetType();
            //如果两个T类型不一样  就不作比较
            if (!typeOne.Equals(typeTwo)) { return false; }
            PropertyInfo[] pisOne = typeOne.GetProperties(); //获取所有公共属性(Public)
            PropertyInfo[] pisTwo = typeTwo.GetProperties();
            //如果长度为0返回false
            if (pisOne.Length <= 0 || pisTwo.Length <= 0)
            {
                return false;
            }
            //如果长度不一样,返回false
            if (!(pisOne.Length.Equals(pisTwo.Length))) { return false; }
            //遍历两个T类型,遍历属性,并作比较
            for (int i = 0; i < pisOne.Length; i++)
            {
                //获取属性名
                string oneName = pisOne[i].Name;
                string twoName = pisTwo[i].Name;
                //获取属性的值
                object oneValue = pisOne[i].GetValue(oneT, null);
                object twoValue = pisTwo[i].GetValue(twoT, null);
                //比较,只比较值类型
                if ((pisOne[i].PropertyType.IsValueType || pisOne[i].PropertyType.Name.StartsWith("String")) && (pisTwo[i].PropertyType.IsValueType || pisTwo[i].PropertyType.Name.StartsWith("String")))
                {
                    if (oneName.Equals(twoName))
                    {
                        if (oneValue == null)
                        {
                            if (twoValue != null)
                            {
                                result = false;
                                break; //如果有不一样的就退出循环
                            }
                        }
                        else if (oneValue != null)
                        {
                            if (twoValue != null)
                            {
                                if (!oneValue.Equals(twoValue))
                                {
                                    result = false;
                                    break; //如果有不一样的就退出循环
                                }
                            }
                            else if (twoValue == null)
                            {
                                result = false;
                                break; //如果有不一样的就退出循环
                            }
                        }
                    }
                    else
                    {
                        result = false;
                        break;
                    }
                }
                else
                {
                    //如果对象中的属性是实体类对象,递归遍历比较
                    bool b = CompareType(oneValue, twoValue);
                    if (!b) { result = b; break; }
                }
            }
            return result;
        }
    }

    public class Test
    {
        public string Names { get; set; }
        public int Age { get; set; }
        public float Height { get; set; }
    }
}

输出:是否相同:False

将这两个 Test 对象的值改为一样试试

static void Main(string[] args)
{
    Test test1 = new Test();
    test1.Names = "张三";
    test1.Age = 24;
    test1.Height = 179.9f;
    Test test2 = new Test();
    test2.Names = "张三";
    test2.Age = 24;
    test2.Height = 179.9f;

    bool result = CompareType(test1, test2);
    Console.WriteLine("是否相同:" + result);

    Console.ReadKey();
}

输出:是否相同:True

可以看到,输出结果是对的

结束

如果这个帖子对你有用,欢迎 关注 + 点赞 + 留言,谢谢

end

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C# 泛型详解(泛型类,方法,接口,委托,约束,反射 ) 的相关文章

随机推荐

  • 解决docker启动mysql无法输入中文以及中文不显示或乱码问题

    前言 我在使用MySQL时 遇到了两个问题 一是在插入中文数据时 无法输入中文 二是在select的时候 查出来的中文数据是空的 因为插入时为空 然后我就使用Navicat连接数据库添加了中文数据 再到docker中查询 就发现了乱码问题
  • LeetCode64. 最小路径和

    题目大意 求出从网络左上角到右下角的一条代价最小的路径和 题目分析 使用动态规划 求出左上角到网络中每个点的代价最小路径和 假设当前要求的是point i j 点 那么它的值就应该是从左上角到它上面那个点point i 1 j 的路径和 与
  • 【技术应用】Qt Creator使用体会与小技巧

    Qt Creator是Qt官方的IDE 这个IDE为Qt编程人员提供了一个完整的开发环境 当然了 这个IDE是用Qt写的 也是免费的 这个IDE真正的编译部分使用了MinGW gcc compiler 也就是说 这个IDE主要的作用是协助开
  • 教务管理系统(免费源码获取)

    项目介绍 本系统使用springboot mybatis plus shiro lombok等技术 使用json传递数据 使用加盐加密对数据进行保存 前端页面使用vue搭建并打包放在static文件夹中 使用token保存当前用户 当用户登
  • chrome浏览器network报错:ERR_CERT_AUTHORITY_INVALID

    转载请注明作者 独孤尚良dugushangliang 出处 https blog csdn net dugushangliang article details 85275319 在访问局域网的某网址时 提示 您的连接不是私密连接 错误代码
  • 算法在ros中应用_烟火检测算法——中伟视界人工智能算法AI在智慧工地、石油中的应用_腾讯新闻...

    烟火检测算法功能说明及实现原理等 一 软件概述 视频智能分析基于目前先进的深度学习算法 通过大量的项目现场素材训练模型 通过本站大量采集的工作服素材 高精度的识别人 安全帽 工作服等识别 本项目主要两方面的算法 一是识别类的 二是行为分析
  • WPF中Datagrid其中一列使用图片显示

    实现效果 实现遇到的问题 当时想要实现如图所示 合格率 所示的效果 我的第一个想法就是使用wpf的转换器 可是接下来问题来了 我这个是通过数值来判断是否合格 什么控件可以做到既可以绑定图片类型的 又可以绑定数值类型的 还有此时的当值绑定肯定
  • 段、页、页框、页表、页表项

    段 页 页框 页表 页表项 分页式虚拟内存 页 页框 页表 页表项 段页式虚拟内存 分段 分页 段 段表 段表项 页 页框 页表 页表项 分页式虚拟内存 页 页框 页表 页表项 页 进程中的块 进程被分成许多大小相同的块 页号 页框 内存中
  • TS2769: Property 'xxx' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttribute...

    用TypeScript开发React项目 在父子组件间传值时发生错误提示 class Page extends React Component render return div div
  • vue组件利用css var(--变量)实现动态修改伪类属性(::before、::after)

    如图所示 1 我们可以利用此属性实现vue组件动态传值 修改例如 before after等 伪类的背景色 背景图等属性值 因为vue利用无法直接在css中使用data里的变量 利用var 变量名 以及style中定义变量 其实此步是模仿
  • Coordinate attention,SE,CBAM

    1 SE 因为普通卷积难以建模信道关系 SE考虑通道的相互依赖关系增强模型对信息通道的敏感性 同时全局平均池化可以帮助模型捕获全局信息 然而SE只考虑了内部通道信息而忽略了位置信息的重要性 输入X首先经过全局平均池化 然后经过全连接层来捕获
  • 静态类和动态类的区别和使用

    1 静态类中的静态方法可以通过类名直接调用静态方法 不需要实例化对象 但是无法和Spring容器中的bean进行交互 例如 Slf4j public class ExcelUtil public static
  • 动态链接

    动态链接 命令 gcc static 产生静态库 shared 产生共享库 readelf d 查看 dynamic段的内容 ldd 查看一个程序主模块或一个共享库依赖于哪些共享库 一 静态链接和动态链接的优缺点 静态链接 空间的浪费 静态
  • Arduino结合HX711实现8路信号采集称重

    说明 使用两块Arduino实现8路Sensor同时采集 并输出控制信号 写作目的主要是为了作为学习笔记 Arduino Sensor接线图 1 双机通讯连线图 2 HX711和Sensor的连线图 3 将8个Sensor的SCK全部接到r
  • 键盘输入_bp

    依据惯例 仍然感谢出处 来自程序员的暴击 https space bilibili com 128373173 学习了下 这个说了个什么呢 人到达灯附近 显示提示文字 按F键开灯和关灯切换 远离灯时 提示文字消失 不能切换灯的切换开关状态
  • QT编译报错无法解析的外部符号

    QT编译报错无法解析的外部符号 特征 头文件 有几个槽函数 提示有多少个无法解析的外部符号 注释掉宏Q OBJECT 可以编译通过 可能原因 1 对应的cpp文件没有加入项目中 2 cpp文件 右键属性 为 自定义工具 没有进行编译 修改为
  • 华为、华三、锐捷、飞塔、山石的抓包命令

    一 华为的抓包命令 1 基本概念 华为的抓包行为称之为镜像端口 也就是说将需要抓取的接口上 称为镜像端口 的流量复制一份到另一个接口上 工程师进行流量观察的端口 称为观察端口 如下图所示 2 华为镜像端口分类 1 本地镜像端口 也就是观察端
  • Django框架:优缺点、实用场景及与Flask、FastAPI的对比

    Django是一个使用Python语言编写的高级Web框架 它提供了快速开发 可重用和可维护的Web应用程序所需的一切组件 在本文中 我们将探讨Django的get和post请求 优缺点 实用场景以及与Flask FastAPI的对比 Dj
  • windows中如何将收藏夹里的下载链接加入到开始

    windows中如何将收藏夹里的下载链接加入到开始 以windows 7为例 设置方法如下 1 右击工具栏 属性 2 开始菜单 自定义 下拉至下载 点中显示为链接 确定 3 可以看到 下载已经看到了
  • C# 泛型详解(泛型类,方法,接口,委托,约束,反射 )

    目录 一 什么是泛型 二 为什么要用泛型 三 泛型和Object类型的区别 四 泛型类 五 泛型方法 六 泛型接口 七 泛型委托 八 泛型约束 九 泛型配合反射 结束 一 什么是泛型 先看一段介绍 泛型 Generic 是将不确定的类型预先