当前位置: 首页 > news >正文

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

目录

一、什么是泛型

二、为什么要用泛型

三、泛型和Object类型的区别

四、泛型类

1.泛型类案例

2.泛型类继承

五、泛型方法

六、泛型接口

七、泛型委托

八、泛型约束

九、泛型配合反射

结束


一、什么是泛型

先看一段介绍

泛型(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)
        {
            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 去判断吧,假设其他类也要做这样的判断呢?这个案例就教你如何来解决这个问题。

代码

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

相关文章:

  • 【云原生】基于Kubernetes的阿里云ACK网络管理
  • Ant Design Vue3中DatePicker 日期选择框如何将接收的数据显示在日期选择框中
  • java计算机毕业设计体育城场地预定系统前台源码+系统+数据库+lw文档+mybatis+运行部署
  • 猿创征文|『单片机原理』程序存储器的结构
  • 【Java 基础】6、Java 对象在 JVM 中的内存布局(详细说明)
  • 不信谣不传谣,亲自动手验证ModelBox推理是否真的“高性能”
  • 树的先序、中序、后序遍历
  • 猿创征文|基于Java+SpringBoot+vue学生课程学习交流考试系统详细设计实现
  • 自动化测试:电商管理系统元素定位练习
  • C# 后端以form格式请求上传附件
  • vue3之provide个inject
  • Es修改索引别名
  • JS 分支语句与循环语句(扩展TODO)
  • 搜狗输入法输入上下标
  • Python「面向对象基本语法2」引用概念、方法中的self参数、代码示例
  • 《用数据讲故事》作者Cole N. Knaflic:消除一切无效的图表
  • C++回声服务器_9-epoll边缘触发模式版本服务器
  • Create React App 使用
  • Java多态
  • Linux后台研发超实用命令总结
  • Linux快速复制或删除大量小文件
  • Selenium实战教程系列(二)---元素定位
  • 阿里云Kubernetes容器服务上体验Knative
  • - 概述 - 《设计模式(极简c++版)》
  • 聊聊flink的TableFactory
  • 深入体验bash on windows,在windows上搭建原生的linux开发环境,酷!
  • 实现简单的正则表达式引擎
  • 数据仓库的几种建模方法
  • 数组大概知多少
  • 积累各种好的链接
  • ​猴子吃桃问题:每天都吃了前一天剩下的一半多一个。
  • #我与Java虚拟机的故事#连载03:面试过的百度,滴滴,快手都问了这些问题
  • (iPhone/iPad开发)在UIWebView中自定义菜单栏
  • (八十八)VFL语言初步 - 实现布局
  • (二)PySpark3:SparkSQL编程
  • (力扣)循环队列的实现与详解(C语言)
  • (三分钟了解debug)SLAM研究方向-Debug总结
  • (十八)devops持续集成开发——使用docker安装部署jenkins流水线服务
  • (一)为什么要选择C++
  • (转) Face-Resources
  • (转载)微软数据挖掘算法:Microsoft 时序算法(5)
  • .NET 5种线程安全集合
  • .NET CF命令行调试器MDbg入门(四) Attaching to Processes
  • .NET Core工程编译事件$(TargetDir)变量为空引发的思考
  • .NET设计模式(11):组合模式(Composite Pattern)
  • .net专家(张羿专栏)
  • @ 代码随想录算法训练营第8周(C语言)|Day53(动态规划)
  • [100天算法】-实现 strStr()(day 52)
  • [2018-01-08] Python强化周的第一天
  • [Ariticle] 厚黑之道 一 小狐狸听故事
  • [ASP.NET MVC]Ajax与CustomErrors的尴尬
  • [AutoSar]BSW_Com02 PDU详解
  • [AutoSar]BSW_OS 01 priority ceiling protocol(PCP)
  • [C++]C++基础知识概述
  • [Contiki系列论文之2]WSN的自适应通信架构