C#泛型详解及关键字作用

 更新时间:2021年8月17日 00:00  点击:1960

这篇文章主要来讲讲c#中的泛型,因为泛型在c#中有很重要的位置,对于写出高可读性,高性能的代码有着关键的作用。

一、什么是泛型?

泛型是 2.0 版 C# 语言和公共语言运行库 (CLR) 中的一个非常重要的新功能。

我们在编程程序时,经常会遇到功能非常相似的模块,只是它们处理的数据不一样。但我们没有办法,只能分别写多个方法来处理不同的数据类型。这个时候,那么问题来了,有没有一种办法,用同一个方法来处理传入不同种类型参数的办法呢?泛型的出现就是专门来解决这个问题的,可以看出,微软还是很贴心的。

二、为什么要使用泛型?

接下来我们来看一段代码。

public class GenericClass
    {
        public void ShowInt(int n)
        {
            Console.WriteLine("ShowInt print {0},ShowInt Parament Type Is {1}",n,n.GetType());
        }
        public void ShowDateTime(DateTime dt)
        {
            Console.WriteLine("ShowDateTime print {0},ShowDateTime Parament Type Is {1}", dt, dt.GetType());
        }
        public void ShowPeople(People people)
        {
            Console.WriteLine("ShowPeople print {0},ShowPeople Parament Type Is {1}", people, people.GetType());
        }
    }

static void Main(string[] args)
        {
            GenericClass generice = new GenericClass();
            generice.ShowInt(11);
            generice.ShowDateTime(DateTime.Now);
            generice.ShowPeople(new People { Id = 11, Name = "Tom" });

            Console.ReadKey();
        }

显示结果:

我们可以看出这三个方法,除了传入的参数不同外,其里面实现的功能都是一样的。在1.1版的时候,还没有泛型这个概念,那么怎么办呢。就有人想到了OOP三大特性之一的继承,我们知道,C#语言中,object是所有类型的基类,将上面的代码进行以下优化:

public class GenericClass
    {
        public void ShowObj(object obj)
        {
            Console.WriteLine("ShowObj print {0},ShowObj Parament Type Is {1}", obj, obj.GetType());
        }
    }
        static void Main(string[] args)
        {
            Console.WriteLine("*****************object调用*********************");
            generice.ShowObj(11);
            generice.ShowObj(DateTime.Now);
            generice.ShowObj(new People { Id = 11, Name = "Tom" });

            Console.ReadKey();
        }

显示结果:

我们可以看出,目地是达到了。解决了代码的可读性,但是这样又有个不好的地方了,我们这样做实际上是一个装箱拆箱操作,会损耗性能。

终于,微软在2.0的时候发布了泛型。接下来我们用泛型方法来实现该功能。

三、泛型类型参数

在使用泛型方法之前,我们先来了解下有关于泛型的一些知识。

在泛型类型或方法定义中,类型参数是在其实例化泛型类型的一个变量时,客户端指定的特定类型的占位符。 泛型类(GenericList<T>)无法按原样使用,因为它不是真正的类型;它更像是类型的蓝图。 若要使用GenericList<T>,客户端代码必须通过指定尖括号内的类型参数来声明并实例化构造类型。 此特定类的类型参数可以是编译器可识别的任何类型。 可创建任意数量的构造类型实例,其中每个使用不同的类型参数,如下所示:

GenericList<float> list1 = new GenericList<float>();
GenericList<ExampleClass> list2 = new GenericList<ExampleClass>();
GenericList<ExampleStruct> list3 = new GenericList<ExampleStruct>();

GenericList<T>的每个实例中,类中出现的每个T在运行时均会被替换为类型参数。 通过这种替换,我们已通过使用单个类定义创建了三个单独的类型安全的有效对象。

三、泛型约束

定义泛型类时,可以对客户端代码能够在实例化类时用于类型参数的几种类型施加限制。 如果客户端代码尝试使用约束所不允许的类型来实例化类,则会产生编译时错误。 这些限制称为约束。 通过使用where上下文关键字指定约束。 下表列出了六种类型的约束:

where T:结构(类型参数必须是值类型。可以指定除 Nullable 以外的任何值类型。)

class MyClass<U>
        where U : struct///约束U参数必须为“值 类型”
 { }

 public void MyMetod<T>(T t)
       where T : struct
 {          
 }

where T:类(类型参数必须是引用类型;这一点也适用于任何类、接口、委托或数组类型。)

class MyClass<U>
        where U : class///约束U参数必须为“引用类型”
 { }

 public void MyMetod<T>(T t)
       where T : class
 {          
 }

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

class EmployeeList<T> where T : Employee, IEmployee, System.IComparable<T>, new()
{
    // ...
}

where T:<基类名>(类型参数必须是指定的基类或派生自指定的基类。)

public class Employee{}

public class GenericList<T> where T : Employee

where T:<接口名称>(类型参数必须是指定的接口或实现指定的接口。可以指定多个接口约束。约束接口也可以是泛型的。)

/// <summary>
    /// 接口
    /// </summary>
    interface IMyInterface
    {
    }

    /// <summary>
    /// 定义的一个字典类型
    /// </summary>
    /// <typeparam name="TKey"></typeparam>
    /// <typeparam name="TVal"></typeparam>
    class Dictionary<TKey, TVal>
        where TKey : IComparable, IEnumerable
        where TVal : IMyInterface
    {
        public void Add(TKey key, TVal val)
        {
        }
    }

where T:U(为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。也就是说T和U的参数必须一样)

class List<T>
{
    void Add<U>(List<U> items) where U : T {/*...*/}
}

以上就是对六种泛型的简单示例,当然泛型约束不仅仅适用于类,接口,对于泛型方法,泛型委托都同样适用。

三、泛型方法

public class GenericClass
    {
        public void ShowT<T>(T t)
        {
            Console.WriteLine("ShowT print {0},ShowT Parament Type Is {1}", t, t.GetType());
        }
    }
static void Main(string[] args)
        {
            Console.WriteLine("*****************泛型方法调用*********************");
            generice.ShowT<int>(11);
            generice.ShowT<DateTime>(DateTime.Now);
            generice.ShowT<People>(new People { Id = 11, Name = "Tom" });

            Console.ReadKey();
        }

显示结果:

也是一样的,现在终于实现了我们想要达到的效果了。我们可以看出,无论是什么方式调用,最后我们获取出来的类型都是原始类型。我们知道,用object获取是利用了继承这一特性,当编译器编译的时候,我们传入的参数会进行装箱操作,当我们获取的时候又要进行拆箱操作,这个方法会损耗性能 。那么泛型方法实现的原理又是怎样的呢?首先,我们要知道,泛型是一个语法糖,在我们调用泛型方法,编译器进行编译时,才会确定传入的参数的类型,从而生成副本方法。这个副本方法与原始方法一法,所以不会有装箱拆箱操作,也就没有损耗性能这回事了。

四、泛型类

泛型类封装不特定于特定数据类型的操作。

通常,创建泛型类是从现有具体类开始,然后每次逐个将类型更改为类型参数,直到泛化和可用性达到最佳平衡。

创建自己的泛型类时,需要考虑以下重要注意事项:

  • 要将哪些类型泛化为类型参数。

通常,可参数化的类型越多,代码就越灵活、其可重用性就越高。 但过度泛化会造成其他开发人员难以阅读或理解代码。

  • 要将何种约束(如有)应用到类型参数

其中一个有用的规则是,应用最大程度的约束,同时仍可处理必须处理的类型。 例如,如果知道泛型类仅用于引用类型,则请应用类约束。 这可防止将类意外用于值类型,并    使你可在 T 上使用 as 运算符和检查 null 值。      

  • 是否将泛型行为分解为基类和子类。

因为泛型类可用作基类,所以非泛型类的相同设计注意事项在此也适用。 请参阅本主题后文有关从泛型基类继承的规则。

  • 实现一个泛型接口还是多个泛型接口。

class BaseNode { }
class BaseNodeGeneric<T> { }

// concrete type
class NodeConcrete<T> : BaseNode { }

//closed constructed type
class NodeClosed<T> : BaseNodeGeneric<int> { }

//open constructed type 
class NodeOpen<T> : BaseNodeGeneric<T> { }

五、泛型接口

定义一个泛型接口:

interface IMyGenericInterface<T>
{
}

一个接口可定义多个类型参数,如下所示:

interface IMyGenericInterface<TKey,TValue>
{
}

具体类可实现封闭式构造接口,如下所示:

interface IBaseInterface<T> { }

class SampleClass : IBaseInterface<string> { }//如果T有约束,那么string类型必须得满足T的约束

六、泛型委托

委托可以定义它自己的类型参数。 引用泛型委托的代码可以指定类型参数以创建封闭式构造类型,就像实例化泛型类或调用泛型方法一样,如以下示例中所示:

class Program
    {
        static void Main(string[] args)
        {
            Del<int> m1 = new Del<int>(Notify);
            m1.Invoke(1111);
            Del<string> m2 = new Del<string>(Notify);
            m2.Invoke("字符串");

            Console.ReadKey();
        }

        public delegate void Del<T>(T item);
        public static void Notify(int i) { Console.WriteLine("{0} type is {1}", i,i.GetType()); }
        public static void Notify(string str) { Console.WriteLine("{0} type is {1}", str, str.GetType()); }
       
    }

运行结果:

七、泛型代码中的默认关键字:Default

在泛型类和泛型方法中产生的一个问题是,在预先未知以下情况时,如何将默认值分配给参数化类型 T:

  • T 是引用类型还是值类型。
  • 如果 T 为值类型,则它是数值还是结构。

给定参数化类型 T 的一个变量 t,只有当 T 为引用类型时,语句 t = null 才有效;只有当 T 为数值类型而不是结构时,语句 t = 0 才能正常使用。解决方案是使用default关键字,此关键字对于引用类型会返回空,对于数值类型会返回零。对于结构,此关键字将返回初始化为零或空的每个结构成员,具体取决于这些结构是值类型还是引用类型。

namespace MyGeneric
{
    class Program
    {
        static void Main(string[] args)
        {
            object obj1=GenericToDefault<string>();
            object obj2 = GenericToDefault<int>();
            object obj3 = GenericToDefault<StructDemo>();
            Console.ReadKey();
        }
        public static T GenericToDefault<T>() 
        {
            return default(T);
        }
    }
    public struct StructDemo
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
}

运行结果:

到此这篇关于C#泛型详解的文章就介绍到这了,更多相关C#泛型内容请搜索猪先飞以前的文章或继续浏览下面的相关文章希望大家以后多多支持猪先飞!

[!--infotagslink--]

相关文章

  • 浅谈C# 字段和属性

    这篇文章主要介绍了C# 字段和属性的的相关资料,文中示例代码非常详细,供大家参考和学习,感兴趣的朋友可以了解下...2020-11-03
  • C#实现简单的登录界面

    我们在使用C#做项目的时候,基本上都需要制作登录界面,那么今天我们就来一步步看看,如果简单的实现登录界面呢,本文给出2个例子,由简入难,希望大家能够喜欢。...2020-06-25
  • C#中截取字符串的的基本方法详解

    这篇文章主要介绍了C#中截取字符串的的基本方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-11-03
  • C#实现简单的Http请求实例

    这篇文章主要介绍了C#实现简单的Http请求的方法,以实例形式较为详细的分析了C#实现Http请求的具体方法,需要的朋友可以参考下...2020-06-25
  • C#连接SQL数据库和查询数据功能的操作技巧

    本文给大家分享C#连接SQL数据库和查询数据功能的操作技巧,本文通过图文并茂的形式给大家介绍的非常详细,需要的朋友参考下吧...2021-05-17
  • C#中new的几种用法详解

    本文主要介绍了C#中new的几种用法,具有很好的参考价值,下面跟着小编一起来看下吧...2020-06-25
  • 使用Visual Studio2019创建C#项目(窗体应用程序、控制台应用程序、Web应用程序)

    这篇文章主要介绍了使用Visual Studio2019创建C#项目(窗体应用程序、控制台应用程序、Web应用程序),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2020-06-25
  • C#开发Windows窗体应用程序的简单操作步骤

    这篇文章主要介绍了C#开发Windows窗体应用程序的简单操作步骤,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-04-12
  • C#从数据库读取图片并保存的两种方法

    这篇文章主要介绍了C#从数据库读取图片并保存的方法,帮助大家更好的理解和使用c#,感兴趣的朋友可以了解下...2021-01-16
  • C#和JavaScript实现交互的方法

    最近做一个小项目不可避免的需要前端脚本与后台进行交互。由于是在asp.net中实现,故问题演化成asp.net中jiavascript与后台c#如何进行交互。...2020-06-25
  • C++调用C#的DLL程序实现方法

    本文通过例子,讲述了C++调用C#的DLL程序的方法,作出了以下总结,下面就让我们一起来学习吧。...2020-06-25
  • 轻松学习C#的基础入门

    轻松学习C#的基础入门,了解C#最基本的知识点,C#是一种简洁的,类型安全的一种完全面向对象的开发语言,是Microsoft专门基于.NET Framework平台开发的而量身定做的高级程序设计语言,需要的朋友可以参考下...2020-06-25
  • C#变量命名规则小结

    本文主要介绍了C#变量命名规则小结,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-09-09
  • C# 中如何取绝对值函数

    本文主要介绍了C# 中取绝对值的函数。具有很好的参考价值。下面跟着小编一起来看下吧...2020-06-25
  • C#绘制曲线图的方法

    这篇文章主要介绍了C#绘制曲线图的方法,以完整实例形式较为详细的分析了C#进行曲线绘制的具体步骤与相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下...2020-06-25
  • c#自带缓存使用方法 c#移除清理缓存

    这篇文章主要介绍了c#自带缓存使用方法,包括获取数据缓存、设置数据缓存、移除指定数据缓存等方法,需要的朋友可以参考下...2020-06-25
  • c#中(&&,||)与(&,|)的区别详解

    这篇文章主要介绍了c#中(&&,||)与(&,|)的区别详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-06-25
  • C#学习笔记- 随机函数Random()的用法详解

    下面小编就为大家带来一篇C#学习笔记- 随机函数Random()的用法详解。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2020-06-25
  • 经典实例讲解C#递归算法

    这篇文章主要用实例讲解C#递归算法的概念以及用法,文中代码非常详细,帮助大家更好的参考和学习,感兴趣的朋友可以了解下...2020-06-25
  • C#中list用法实例

    这篇文章主要介绍了C#中list用法,结合实例形式分析了C#中list排序、运算、转换等常见操作技巧,具有一定参考借鉴价值,需要的朋友可以参考下...2020-06-25