Bootstrap

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

目录

一、什么是泛型

二、为什么要用泛型

三、泛型和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

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;