勇哥的C#知识经验圈点:泛型

勇哥注:

这个系列的贴子主要是为了培训用(专职自动化设备的C#软件工程师),因此例子的知识范围被限制在行业范围内。

C#基础知识在网上是最多的存在,不足主要下面几点:

1. 内容零碎,没有大纲来组织
2. 什么功能都有讲,就是没有按实际项目的常用程度来组织
3. PLC转上位机的人员,很难把PLC的编程思想转到C#编程上来,需要在知识点讲解上对此问题有点拔

勇哥的这套贴子会有大纲,主要特点是补足以上几点问题,另外本套贴子属于经验性质的圈点知识,不属于完全小白式的教学。

如果读者有好的意见,请留言反馈。


泛型

C#的“泛型”是一种编程技术,可以让你在编写代码的时候,不需要指定具体的数据类型,
而是使用一个类型参数来定义一个泛型类型或方法。


举例:

比如.net FrameWork的 List<T>类,其中T是一个类型参数,而不是具体的数据类型。
当你使用这个List<T>类时,你可以指定T的具体类型,例如int、string或者其他任何你想要的类型。
这样,你就可以在不同的情况下使用相同的代码,而不需要写多份代码。

public List<string> list1=new List<string>();
public List<int> list2=new List<int>();



目录:

(1)一个功能类要处理的数据类型不确定

        (1.2)限制上面泛型类的数据类型,比如说只允许T为值类型。

        (1.3) 继承泛型类的写法

(2)多类型泛型

(3)泛型委托

(4)泛型接口

        (4.2) 什么是协变?

        (4.3) 泛型委托的协变

        (4.4) 自定义协变

        (4.5) 逆变

(5)泛型缓存

(6)泛型函数返回T类型

(7)泛型特点总结

(8)延伸阅读参考


(1)一个功能类要处理的数据类型不确定


这是最基本的应用,一个类它的功能可能需要处理无数个数据类型的版本。

此时泛型的类将数据类型用T来占位,在使用的时候由使用者指定数据类型。


示例1:泛型类

注意类的名字后面要加上<T>,否则类里面你用T时编辑器会提示非法。

    public class Class泛型1<T>
    {
        private T[] buffer;

        public Class泛型1(int size)
        {
            buffer = new T[size+1];
        }

        public T GetValue(int index)
        {
            return buffer[index];
        }

        public void SetValue(int index,T data)
        {
            buffer[index] = data;
        }
    }

调用者:

//一个功能类要处理的数据类型不确定
Class泛型1<int> ary1 = new Class泛型1<int>(5);
for(int i=0;i<5;i++)
{
    ary1.SetValue(i, i * 5);
}

Class泛型1<string> ary2 = new Class泛型1<string>(5);
for (int i = 0; i < 5; i++)
{
    ary2.SetValue(i, (i * 5).ToString());
}

ary2的结果如下:

image.png


示例2:

限制上面泛型类的数据类型,比如说只允许T为值类型。


改动代码:(和上面代码唯一区别是加了where语句) 

   public class Class泛型2<T> where T:struct
    {
        private T[] buffer;

        public Class泛型2(int size)
        {
            buffer = new T[size + 1];
        }

        public T GetValue(int index)
        {
            return buffer[index];
        }

        public void SetValue(int index, T data)
        {
            buffer[index] = data;
        }
    }

调用者:

//泛型限制数据类型
Class泛型2<string> ary3 = new Class泛型2<string>(5);

此时VS编辑器会直接报错。

因为string是引用类型。

image.png


where限制的内容可以下面的:

where T: struct   //值类型

where T: Class    //引用类型

where T: Interface   //接口类型

where T: 员工类      //具体的一个类,这是一个根类

where T: 开发组员工类    //具体的一个类,这个是员工类的子类

where T: new()    //无参数构造函数约束

where T : People, ISports, IWork, new()   //泛型约束也可以同时约束多个,此时new()约束一定是在最后




示例3:

继承泛型类的写法

//(1) 指定基类类型的写法,此时Myson类将不再是个泛型类
public class Myson: Class泛型2<int>
{
  public Myson(int size):base(size)
  {

  }
}

//(2) 继承基类泛型类的写法
    public class Myson2<T> : Class泛型2<T> where T : struct
    {
        public Myson2(int size) : base(size)
        {

        }
    }

注意:由于基类有构造函数,所以派生类不写构造函数的话,会报错:

image.png


示例4:泛型类中使用泛型函数

   public class Class泛型2<T> where T:struct
    {
        private T[] buffer;

        public Class泛型2(int size)
        {
            buffer = new T[size + 1];
        }

        public T GetValue(int index)
        {
            return buffer[index];
        }

        public void SetValue(int index, T data)
        {
            buffer[index] = data;
        }

        public string GetString<T3>(T3 data)
        {
            return data.ToString();
        }
    }

注意最后一个成员函数,有一个T3,它跟泛型类定义的T是不同的。



(2)多类型泛型


下面示例是一个使用多类型的泛型函数。(泛型函数只能是静态)

需求是:把指定的枚举中文翻译成对应的英文。

类:

        public enum UserEnableChina
        {
            用户启用 = 0,
            用户禁用 = 1,
        }

        public enum UserEnableEnglish
        {
            User_Enabled = 0,
            User_Disable = 1,
        }

 public class Class泛型2
    {

        /// <summary>
        ///  中文转换成英文的公共方法 
        /// </summary>
        /// <typeparam name="T1"></typeparam>
        /// <typeparam name="T2"></typeparam>
        /// <param name="result"></param>
        /// <param name="EnglishStr"></param>
        public static string ConvertChineseToEnglish<T1, T2>(string result)
        {
            string englishStr = "The_result_does_not_exist";
            string[] array = Enum.GetNames(typeof(T1));
            int i = 0;
            while (i < array.Length)
            {
                if (array[i] == result)
                {
                    string[] engStr = Enum.GetNames(typeof(T2));
                    englishStr = engStr[i];
                    break;
                }
                i++;
            }
            return englishStr.Replace("_", " ");
        }

调用者:

var value = Class泛型2.ConvertChineseToEnglish<UserEnableChina, UserEnableEnglish>("用户禁用");

image.png

注意一点:T1, T2只是建议用名,你完全写成PP1,YY2


(3)泛型委托


示例1:简单泛型委托

类:

    public class Class泛型5
    {
        public delegate void FunDelegate<T>(T msg); 

        public static void FunOutMsg1(string msg1)
        {
            Console.WriteLine(msg1);
        }

        public static void FunOutMsg2(int msg1)
        {
            Console.WriteLine(msg1.ToString());
        }
    }

调用者:

//泛型委托
Class泛型5.FunDelegate<string> del;
del = Class泛型5.FunOutMsg1;
del("hello");

Class泛型5.FunDelegate<int> del2;
del2 = Class泛型5.FunOutMsg2;
del2(55);



(4)泛型接口


示例1:泛型接口

 泛型接口对于泛型集合十分有用。例如使用 IComparable<T> 而不使用 IComparable,这样可以避免值类型的装箱和取消装箱操作。常见的泛型集合见: System.Collections.Generic 命名空间。


泛型接口的例子:

public interface ICollection<T> : IEnumerable<T>
{
    T this[int index] { get; }

    void Add(T element);
    void Remove(T element);
    T Find(T element);
    T Find(Predicate<T> predicate);
}

上面用到了.net的一些泛型接口,相了解.net的常见接口看下面的链接:

http://www.skcircle.com/?id=2155



示例2:什么是协变?

   public class 动物
    {
        public int Id { get; set; }
    }

    public class 猫 : 动物
    {
        public string Name { get; set; }
    }

    public class Class泛型6
    {
        public static void MainFun()
        {
            动物 animal = new 动物();
            猫 cat = new 猫();
            动物 animal2 = new 猫();
            List<动物> listAnimal = new List<动物>();
            List<猫> listCat = new List<猫>();

            List<动物> list = new List<猫>();            //(1)

            //下面为协变  (2)
            IEnumerable<动物> list1 = new List<动物>();
            IEnumerable<动物> list2 = new List<猫>();
        }
    }

一只猫属于动物,那么一群猫就属于一群动物吗?(见注释1)

实际上List<动物>和List<猫>根本就不存在父子关系。

这样会抛出一个编译错误,类型无法隐式转换。

image.png

使用IEnumerable就可以让“List<猫>属于IEnumerable<动物>”。这种能力称为“协变”。

IEnumerable是一个泛型接口,它能完成协变,是因为在泛型接口的T前面有一个out关键字修饰,而且T只能是返回值类型,

不能作为参数类型,这就是协变。

使用了协变以后,左边声明的是基类,右边可以声明基类或者基类的子类。

(实际上是枚举器完成了类型转换)

image.png


示例3:泛型委托的协变

Func<动物> func = new Func<猫>(() => null);

Func也可以完成协变,我看来看看它的原型:

果然也是带有out关键字修饰的。

image.png


示例4:自定义协变

public interface ICustomerListOut<out T>
{
   T Get();
}
  
public class CustomerListOut<T> : ICustomerListOut<T>
{
   public T Get()
   {
     return default(T);
   }
}

调用:

ICustomerListOut<动物> customerList1 = new CustomerListOut<动物>();
ICustomerListOut<动物> customerList2 = new CustomerListOut<猫>();


示例5:逆变

在泛型接口的T前面有一个In关键字修饰,而且T只能方法参数,不能作为返回值类型,这就是逆变。请看下面的自定义逆变:

 public interface ICustomerListIn<in T>
 {
      void Show(T t);
 }
 
public class CustomerListIn<T> : ICustomerListIn<T>
{
      public void Show(T t)
      {
      }
}

调用:

ICustomerListIn<猫> customerListCat1 = new CustomerListIn<猫>();
ICustomerListIn<猫> customerListCat2 = new CustomerListIn<动物>();


示例6:协变和逆变同时使用

public interface IMyList<in inT, out outT>
{
     void Show(inT t);
      outT Get();
      outT Do(inT t);
}

public class MyList<T1, T2> : IMyList<T1, T2>
{
 
    public void Show(T1 t)
     {
           Console.WriteLine(t.GetType().Name);
    }
 
    public T2 Get()
     {
          Console.WriteLine(typeof(T2).Name);
          return default(T2);
      }

     public T2 Do(T1 t)
     {
          Console.WriteLine(t.GetType().Name);
          Console.WriteLine(typeof(T2).Name);
           return default(T2);
       }
 }

调用者:

IMyList<猫, 动物> myList1 = new MyList<猫, 动物>();
IMyList<猫, 动物> myList2 = new MyList<猫, 猫>();		//协变
IMyList<猫, 动物> myList3 = new MyList<Animal, 动物>();	//逆变
IMyList<猫, 动物> myList4 = new MyList<Animal, 猫>();	//逆变+协变


(5)泛型缓存


类:

   public class GenericCache<T>
    {
        static GenericCache()
        {
            Console.WriteLine("This is GenericCache 静态构造函数");
            _TypeTime = string.Format("{0}_{1}", typeof(T).FullName, DateTime.Now.ToString("yyyyMMddHHmmss.fff"));
        }

        private static string _TypeTime = "";

        public static string GetCache()
        {
            return _TypeTime;
        }
    }

    public class GenericCacheTest
    {
        public static void Show()
        {
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine(GenericCache<int>.GetCache());
                Thread.Sleep(10);
                Console.WriteLine(GenericCache<long>.GetCache());
                Thread.Sleep(10);
                Console.WriteLine(GenericCache<DateTime>.GetCache());
                Thread.Sleep(10);
                Console.WriteLine(GenericCache<string>.GetCache());
                Thread.Sleep(10);
                Console.WriteLine(GenericCache<GenericCacheTest>.GetCache());
                Thread.Sleep(10);
            }
        }
    }


调用者:

//演示泛型缓存
GenericCacheTest.Show();


结果:

This is GenericCache 静态构造函数    //(1)
System.Int32_20230221185802.105
This is GenericCache 静态构造函数
System.Int64_20230221185802.117
This is GenericCache 静态构造函数
System.DateTime_20230221185802.132
This is GenericCache 静态构造函数
System.String_20230221185802.148
This is GenericCache 静态构造函数

WindowsFormsApp2.GenericCacheTest_20230221185802.164
System.Int32_20230221185802.105                            //(2)
System.Int64_20230221185802.117
System.DateTime_20230221185802.132
System.String_20230221185802.148
WindowsFormsApp2.GenericCacheTest_20230221185802.164
System.Int32_20230221185802.105                            //(3)
System.Int64_20230221185802.117    
System.DateTime_20230221185802.132
System.String_20230221185802.148
WindowsFormsApp2.GenericCacheTest_20230221185802.164
System.Int32_20230221185802.105                            //(4)
System.Int64_20230221185802.117
System.DateTime_20230221185802.132
System.String_20230221185802.148
WindowsFormsApp2.GenericCacheTest_20230221185802.164
System.Int32_20230221185802.105                            //(5)
System.Int64_20230221185802.117
System.DateTime_20230221185802.132
System.String_20230221185802.148
WindowsFormsApp2.GenericCacheTest_20230221185802.164

代码循环5次,第一次的时候由泛型类构造出第(1)个缓存。

然后第2,3,4,5次循环的时候,程序直接读的是缓存数据。

利用这个特性,可用作缓存器。

注意:只能为不同的类型缓存一次。泛型缓存比字典缓存效率高。泛型缓存不能主动释放


(6)泛型函数返回T类型

你的泛型函数想返回类型和参数类型一样,都是T的时候,这个时候需要做一些特殊处理。

因为你返回任何实际类型都不是T,编译会报错。

这个时候注意要给你的泛型函数加上约束。

但是要注意的,你如果把约束写成struct(即值类型)的话,会报下面的错:

由于类型参数"T"即没有类类型约束也没有"class"约束,因此不能与"as"运算符一起使用

public abstract T Cal<T>(T data) where T : struct;

image.png

这是因为如果你想使用as运算符,必须约束为Class类型,即引用类型。


如果希望约事为struct,然后还需要返回T,则请看下面的例子:

private static T Sum<T>(T num1,T num2) where T : struct
        {
            dynamic v1 = num1;
            dynamic v2 = num2;
            return (T)(v1 + v2);
        }

下面是多个T相加返回T的例子:

T Sum<T> (params T [] numbers) where T:struct{
    double total =0;
    foreach (var value in numbers)
        total = total + Double.Parse(value.ToString());
    return (T)Convert.ChangeType(total,typeof(T));}


另外在泛型函数中,你可能需要判断T的数据类型,以方便case做处理。可以如下判断:

使用 typeof operator使用泛型,所以 typeof(T) 将得到 Type T对应的引用,然后使用IsValueType属性:

if (typeof(T).IsValueType)


或者,像下面这样判断是否是引用类型:

if (default(T) == null)



(7)泛型特点总结


使用泛型是一种增强程序功能的技术,具体表现在以下几个方面:

  • 它有助于您最大限度地重用代码、保护类型的安全以及提高性能。

  • 您可以创建泛型集合类。.NET 框架类库在 System.Collections.Generic 命名空间中包含了一些新的泛型集合类。您可以使用这些泛型集合类来替代 System.Collections 中的集合类。

  • 您可以创建自己的泛型接口、泛型类、泛型方法、泛型事件和泛型委托。

  • 您可以对泛型类进行约束以访问特定数据类型的方法。

  • 关于泛型数据类型中使用的类型的信息可在运行时通过使用反射获取。


(8)延伸阅读参考

略。。。




演示源码下载:


支付5元或购买VIP会员后,才能查看本内容!立即支付升级会员查询订单


--------------------- 

作者:hackpig

来源:www.skcircle.com

版权声明:本文为博主原创文章,转载请附上博文链接!



本文出自勇哥的网站《少有人走的路》wwww.skcircle.com,转载请注明出处!讨论可扫码加群:
本帖最后由 勇哥,很想停止 于 2023-02-25 11:03:10 编辑

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

会员中心
搜索
«    2025年4月    »
123456
78910111213
14151617181920
21222324252627
282930
网站分类
标签列表
最新留言
    热门文章 | 热评文章 | 随机文章
文章归档
友情链接
  • 订阅本站的 RSS 2.0 新闻聚合
  • 扫描加本站机器视觉QQ群,验证答案为:halcon勇哥的机器视觉
  • 点击查阅微信群二维码
  • 扫描加勇哥的非标自动化群,验证答案:C#/C++/VB勇哥的非标自动化群
  • 扫描加站长微信:站长微信:abc496103864
  • 扫描加站长QQ:
  • 扫描赞赏本站:
  • 留言板:

Powered By Z-BlogPHP 1.7.2

Copyright Your skcircle.com Rights Reserved.

鄂ICP备18008319号


站长QQ:496103864 微信:abc496103864