泛型 generic

//本文是根据C#编程指南修改的。编程指南很多是机器翻译的,很多句子根本读不懂。所以本人就把读不懂地方标出来。

不理解  :1、defalut约束  2、泛型接口不理解的地方  3、泛型方法中的不理解的地方

 一、文章目录

1、泛型的简介绍

2、泛型类型参数命名指南 
3、类型参数的约束

         3.1为什么要使用类型参数约束

        3.2未使用类型参数约束会怎么样

  3.3参数约束的位置顺序
4、泛型类
5、泛型接⼝
6、泛型⽅法
7、泛型和数组
8、泛型委托
9、运⾏时中的泛型
10、泛型和反射
11、泛型和特性

12、协变和逆变

13、C#委托Action、Action<T>、Func<T>、Predicate<T>

14、泛型和可空引用类型特性

C#泛型机制简介


C#泛型能力由CLR在运行时支持。这使得泛型能力可以在各个支持CLR的语言之间进行无缝的互操作。
C#泛型代码在被编译为IL 代码和元数据时,采用特殊的占位符来表示泛型类型,并用专有的IL 指令支持泛型操作。而真正的泛型实例化工作以“on-demand” 的方式,发生在JIT编译时。

总结:

泛型 就是 把具体的数据类型和算法隔离开

1、C#的泛型能力由CLR在运行时支持,它既不同于C++在编译时所支持的静态模板,也不同于Java在编译器层面使用“擦拭法”支持的简单的泛型。

2、C#的泛型支持包括类、结构、接口、委托四种泛型类型,以及方法成员。

3、C#的泛型采用“基类,接口,构造器,值类型/引用类型”的约束方式来实现对类型参数的“显式约束”,它不支持C++模板那样的基于签名的隐式约束。

4、泛型类 接口是在JIL级别完成的。泛型方法是在编译器级别完成的,所以泛型方法(无参方法除外)可以使用类型推断。

泛型类型参数

在泛型类型或泛型方法的定义中,类型参数是一个占位符(placeholder),通常为一个大写字母,如T。在客户代码声明、实例化该类型的变量时,把T替换为客户代码所指定的数据类型。

 类型参数命名指南

 请使用描述性名称命名泛型类型参数,除非单个字母名称完全具有自我说明性且描述性名称不会增加任 何作用

public interface ISessionChannel<TSession> { /*...*/ }
public delegate TOutput Converter<TInput, TOutput>(TInput from);
public class List<T> { /*...*/ }

对具有单个字母类型参数的类型,考虑使用 T 作为类型参数名称。

public int IComparer<T>() { return 0; }
public delegate bool Predicate<T>(T item);
public struct Nullable<T> where T : struct { /*...*/ }

在类型参数描述性名称前添加前缀 "T"。

public interface ISessionChannel<TSession>
{
TSession Session { get; }
}

请考虑在参数名称中指示出类型参数的约束。 例如,约束为 ISession 的参数可命名为 TSession 。  

类型参数的约束

 为什么要使用类型参数约束?

1、为了是在泛型中调用类型参数的方法

如果你把类型参数设置为class 约束,那么你就可以对参数类型进行 as上使用 as 运算符和检查 null 值。
如果你把类型参数设置为struact越苏,那么你就可以对参数类型就行 + - * /运算。

如果你未设置参数约束,那么你默认约束就是Object。你就不能对参数进行== 、!= 运算符操作

 

 

 

 

 

 枚举约束
从 C# 7.3 开始,还可指定 System.Enum 类型作为基类约束。 CLR 始终允许此约束,但 C# 语言不允许。

委托约束
同样从 C# 7.3 开始,可将 System.Delegate 或 System.MulticastDelegate 用作基类约束。 CLR 始终允许此约束,
但 C# 语言不允许。

未绑定的类型参数(未设置类型参数约束)
没有约束的类型参数(如公共类 SampleClass<T>{} 中的 T)称为未绑定的类型参数。 未绑定的类型参数具有以下
规则:
不能使用 != 和 == 运算符,因为无法保证具体的类型参数能支持这些运算符。
可以在它们与 System.Object 之间来回转换,或将它们显式转换为任何接口类型。
可以将它们与 null 进行比较。 将未绑定的参数与 null 进行比较时,如果类型参数为值类型,则该比较将始
终返回 false。

约束类型的位置顺序

类》接口》new()

泛型约束互斥

unmanage\new\struact 这三者互斥

约束多个参数

可以对多个参数应用约束,并对一个参数应用多个约束,如下面的示例所示:

class Base { }
class Test<T, U>
    where U : struct
    where T : Base, new() { }

【注意到点】

1、在应用 where T : class 约束时,避免对类型参数使用 == 和 != 运算符,因为这些运算符仅测试引用同一性而不测试值相等性。即使在用作参数的类型中重载这些运算符也是如此。下面的代码说明了这一点;即使 String 类重载 == 运算符,输出也为 false。

public static void OpTest<T>(T s, T t) where T : class
{
    System.Console.WriteLine(s == t);
}
static void Main()
{
    string s1 = "target";
    System.Text.StringBuilder sb = new System.Text.StringBuilder("target");
    string s2 = sb.ToString();
    OpTest<string>(s1, s2);//false
}

这种情况的原因在于,编译器在编译时仅知道 T 是引用类型,因此必须使用对所有引用类型都有效的默认运算符。如果必须测试值相等性,建议的方法是同时应用 where T : IComparable<T> 约束,并在将用于构造泛型类的任何类中实现该接口。然后就可以用compareto方法比较

2、where T:<base class> 和where T:U的区别  前者是继承同一基类,后者是T、U是继承关系

3、defalut 目前害搞清楚怎么用

 泛型类

 封闭式构造类型和建开放式构造类型

使用一个或多个类型形参的构造类型(例如 List﹤T﹥)称为开放构造类型 (open constructed type)。不使用类型形参的构造类型(例如 List﹤int﹥)称为封闭构造类型

泛型类继承

1、泛型类可继承自具体的封闭式构造或开放式构造基类:

2、非泛型类(即,具体类)可继承自封闭式构造基类,但不可继承自开放式构造类或类型参数,因为运行时客户端代
码无法提供实例化基类所需的类型参数。

class BaseNode { }
class BaseNodeGeneric<T> { }
// concrete type
class NodeConcrete<T> : BaseNode { }
//封闭式式构造类型
class NodeClosed<T> : BaseNodeGeneric<int> { }
//开放式构造类型
class NodeOpen<T> : BaseNodeGeneric<T> { }

3、继承自开放式构造类型的泛型类必须对非此继承类共享的任何基类类型参数提供类型参数,如下方代码所示:

class BaseNodeMultiple<T, U> { }
//No error
class Node4<T> : BaseNodeMultiple<T, int> { }
//No error
class Node5<T, U> : BaseNodeMultiple<T, U> { }
//Generates an error
//class Node6<T> : BaseNodeMultiple<T, U> {}

(我不理解 这个是什么意思)4、继承自开放式构造类型的泛型类必须指定作为基类型上约束超集或表示这些约束的约束:

class NodeItem<T> where T : System.IComparable<T>, new() { }
class SpecialNodeItem<T> : NodeItem<T> where T : System.IComparable<T>, new() { }

5、泛型类型可使用多个类型参数和约束,如下所示:

class SuperKeyType<K, V, U>
where U : System.IComparable<U>
where V : new()
{ }

6、开放式构造和封闭式构造类型可用作方法参数:

void Swap<T>(List<T> list1, List<T> list2)
{
//code to swap items
}
void Swap(List<int> list1, List<int> list2)
{
//code to swap items
}

如果一个泛型类实现一个接口,则该类的所有实例均可强制转换为该接口。
泛型类是不变量。 换而言之,如果一个输入参数指定 List<BaseClass> ,且你尝试提供 List<DerivedClass> ,则会
出现编译时错误。

泛型接口

【注意】为避免对值类型的装箱和取消装箱操作,泛型类的首选项使用泛型接口,例如 IComparable<T>而不是 IComparable。

可将多个接口指定为单个类型上的约束,如下所示:

class Stack<T> where T : System.IComparable<T>, IEnumerable<T>
{
}

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

interface IDictionary<K, V>
{
}

 

接口的继承

适用于类的继承规则也适用于接口:

interface IMonth<T> { }

interface IJanuary     : IMonth<int> { }  //No error
interface IFebruary<T> : IMonth<int> { }  //No error
interface IMarch<T>    : IMonth<T> { }    //No error
//interface IApril<T>  : IMonth<T, U> {}  //Error

 

泛型方法

 泛型方法是通过类型参数声明的方法,如下所示:

static void Swap<T>(ref T lhs, ref T rhs)
{
T temp;
temp = lhs;
lhs = rhs;
rhs = temp;
}
public static void TestSwap()
{
int a = 1;
int b = 2;
}
Swap<int>(ref a, ref b);//可以这样写Swap(ref a, ref b);还可省略类型参数,编译器将推断类型参数
System.Console.WriteLine(a + " " + b);
}

 

类型推理

类型推理发生在编译时,之后编译器尝试解析重载的方法签名。

Swap(ref a, ref b);

类型推理的相同规则适用于静态方法和实例方法。 编译器可基于传入的方法参数推断类型参数;而无法仅根据约
束或返回值推断类型参数。 因此,类型推理不适用于不具有参数的方法。 类型推理发生在编译时,之后编译器尝
试解析重载的方法签名。 编译器将类型推理逻辑应用于共用同一名称的所有泛型方法。 在重载解决方案步骤
中,编译器仅包含在其上类型推理成功的泛型方法。

元组类型做泛型方法参数

public class Test
{

    public  void Post(SendOrPostCallback d, object? state)
    {
        //编译器根据(d,state)推断出s为元组类型
        Swap(s => s.d (s.state)  , (d,state)) ;
      //  Swap2(s => s.d(s.state));无法正确推断除该参数类
    }
 

    static void Swap<T>(Action<T> lhs, T rhs)
    {
        
    }
    static void Swap2<T>(Action<T> lhs)
    {

    }
}

实战案例: public virtual void Post(SendOrPostCallback d, object? state) => ThreadPool.QueueUserWorkItem(static s => s.d(s.state), (d, state), preferLocal: false);

 internal sealed class SynchronizationContextTaskScheduler : TaskScheduler
    {
//其他代码
private readonly SynchronizationContext m_synchronizationContext;
protected internal override void QueueTask(Task task)
        {
            m_synchronizationContext.Post(s_postCallback, (object)task);
        }
//其他代码
 }

  public partial class SynchronizationContext
    {
//其他代码
public virtual void Post(SendOrPostCallback d, object? state) => ThreadPool.QueueUserWorkItem(static s => s.d(s.state), (d, state), preferLocal: false);
 //其他代码
}

 

 

泛型类中的泛型方法

如果在泛型类的内部定义一个 与泛型类相同的类型参数的泛型方法,则编译器会生成警告 CS0693,因为在该方法范围内,
方法中的T(例如 int),会隐藏类中的T(例如 string)的功能。

    class Program
    {
       
        static void Main(string[] args)
        {
            GenericList<int> dfdf = new();
            dfdf.SampleMethod<string>("dfsdfsdf");
        }

    }
  

    class GenericList<T>
    {
        // CS0693 如果在泛型类的内部定义一个 与泛型类相同的类型参数的泛型方法,则编译器会生成警告 CS0693
      public  void SampleMethod<T>(T ise) {

            Console.WriteLine(ise);
        }
    }

(这一段不理解)如果需要使用类型参数(而不是类实例化时提供的参数)调用泛
型类方法所具备的灵活性,请考虑为此方法的类型参数提供另一标识符,如下方示例中 GenericList2<T> 所示。

     static void Main(string[] args)
        {
           

            GenericList2<int>.SampleMethod<string>();
        }

    }
   
    class GenericList2<T>
    {
        //No warning
      public static void SampleMethod<U>() { }
    }

 在泛型方法上使用类型约束

void SwapIfGreater<T>(ref T lhs, ref T rhs) where T : System.IComparable<T>
{
T temp;
if (lhs.CompareTo(rhs) > 0)
{
temp = lhs;
lhs = rhs;
rhs = temp;
}
}

 泛型方法 重载,例如,以下方法可全部位于同一类中:

void DoWork() { }
void DoWork<T>() { }
void DoWork<T, U>() { }

 

 泛型和数组

 

 在 C# 2.0 和更高版本中,下限为零的单维数组自动实现 IList<T>。 这可使你创建可使用相同代码循环访问数组
和其他集合类型的泛型方法。 此技术的主要用处在于读取集合中的数据。 IList<T> 接口无法用于添加元素或从
数组删除元素。 如果在此上下文中尝试对数组调用 IList<T> 方法(例如 RemoveAt),则会引发异常。
如下代码示例演示具有 IList<T> 输入参数的单个泛型方法如何可循环访问列表和数组(此例中为整数数组)。

static void Main()
{
int[] arr = { 0, 1, 2, 3, 4 };
List<int> list = new List<int>();
for (int x = 5; x < 10; x++)
{
list.Add(x);
}
}
ProcessItems<int>(arr);
ProcessItems<int>(list);
static void ProcessItems<T>(IList<T> coll)
{
// IsReadOnly returns True for the array and False for the List.
System.Console.WriteLine
("IsReadOnly returns {0} for this collection.",
coll.IsReadOnly);
// The following statement causes a run-time exception for the
// array, but not for the List.
//coll.RemoveAt(4);
}
}
foreach (T item in coll)
{
System.Console.Write(item.ToString() + " ");
}
System.Console.WriteLine();

泛型和委托

泛型委托( C# 编程指南)
2021/5/24 •
委托可以定义它自己的类型参数。 引用泛型委托的代码可以指定类型参数以创建封闭式构造类型,就像实例化
泛型类或调用泛型方法一样,如以下示例中所示:

public delegate void Del<T>(T item);
public static void Notify(int i) { }
Del<int> m1 = new Del<int>(Notify);


C# 2.0 版具有一种称为方法组转换的新功能,适用于具体委托类型和泛型委托类型,使你能够使用此简化语法编
写上一行:
Del<int> m2 = Notify;
在泛型类中定义的委托可以用类方法使用的相同方式来使用泛型类类型参数。

class Stack<T>
{
T[] items;
int index;
}


public delegate void StackDelegate(T[] items);
引用委托的代码必须指定包含类的类型参数,如下所示:

private static void DoWork(float[] items) { }
public static void TestStack()
{
Stack<float> s = new Stack<float>();
Stack<float>.StackDelegate d = DoWork;
}


根据典型设计模式定义事件时,泛型委托特别有用,因为发件人参数可以为强类型,无需在它和 Object 之间强制
转换。

delegate void StackEventHandler<T, U>(T sender, U eventArgs);
class Stack<T>
{
public class StackEventArgs : System.EventArgs { }
public event StackEventHandler<Stack<T>, StackEventArgs> stackEvent;
}
protected virtual void OnStackChanged(StackEventArgs a)
{
stackEvent(this, a);
}
class SampleClass
{
public void HandleStackChange<T>(Stack<T> stack, Stack<T>.StackEventArgs args) { }
}
public static void Test()
{
Stack<double> s = new Stack<double>();
SampleClass o = new SampleClass();
s.stackEvent += o.HandleStackChange;
}

 

 

泛型和反射

泛型和特征

 

 

协变和逆变

在 C# 中,协变和逆变能够实现数组类型、委托类型和泛型类型参数的隐式引用转换。 协变保留分配兼容性,逆
变则与之相反。
数组的协变使派生程度更大的类型的数组能够隐式转换为派生程度更小的类型的数组。全的操作,如以下代码示例所示。
但是此操作不是类型安

object[] array = new String[10];
// The following statement produces a run-time exception.
// array[0] = 10;

逆变和协变在委托种的作用

对方法组的协变和逆变支持允许将方法签名与委托类型相匹配。 这样,不仅可以将具有匹配签名的方法分配给
委托,还可以分配与委托类型指定的派生类型相比,返回派生程度更大的类型的方法(协变)或接受具有派生程度
更小的类型的参数的方法(逆变)。

static object GetObject() { return null; }
static void SetObject(object obj) { }
static string GetString() { return ""; }
static void SetString(string str) { }
static void Test()
{

Func<object> del = GetString;
Action<string> del2 = SetObject;

}

对 Func 和 Action 泛型委托使用变体 (C#)
 

, C# 支持在泛型接口和委托中使用协变和逆变,并允许隐式转换泛型类型参
数。以下代码示例演示泛型接口的隐式引用转换。

IEnumerable<String> strings = new List<String>();
IEnumerable<Object> objects = strings;

如果泛型接口或委托的泛型参数被声明为协变或逆变,该泛型接口或委托则被称为“变体”。



 

只有引用类型才支持使用泛型接口中的变体。 值类型不支持变体例如,无法将 IEnumerable<int> 隐式转换为
IEnumerable<object> ,因为整数由值类型表示。

IEnumerable<int> integers = new List<int>();
// The following statement generates a compiler error,
// because int is a value type.
// IEnumerable<Object> objects = integers;

 


C#委托Action、Action<T>、Func<T>、Predicate<T>

 

 

C#委托Action、Action<T>、Func<T>、Predicate<T>

 

CLR环境中给我们内置了几个常用委托Action、 Action<T>、Func<T>、Predicate<T>,一般我们要用到委托的时候,尽量不要自己再定义一 个委托了,就用系统内置的这几个已经能够满足大部分的需求,且让代码符合规范。

一、Action

Action封装的方法没有参数也没有返回值,声明原型为:

1 public delegate void Action();

用法如下:

复制代码
1  public void Alert()
2  {
3     Console.WriteLine("这是一个警告");
4  }
5  
6  Action t = new Action(Alert); //  实例化一个Action委托 
7  t();
复制代码

如果委托的方法里的语句比较简短,也可以用Lambd表达式直接把方法定义在委托中,如下:

1 Action t = () => { Console.WriteLine("这是一个警告"); };
2 t();

 

二、Action<T>

Action<T>是Action的泛型实现,支持逆变 也是没有返回值,但可以传入最多16个参数,两个参数的声明原型为:

1 public delegate void Action<in T1, in T2>(T1 arg1, T2 arg2);

用法如下:

复制代码
1 private void ShowResult(int a, int b)
2 {
3     Console.WriteLine(a + b);
4 }
5 
6 Action<int, int> t = new Action<int, int>(ShowResult);//两个参数但没返回值的委托
7 t(2, 3);
复制代码

同样也可以直接用Lambd表达式直接把方法定义在委托中,代码如下: 

1 Action<int, int> t = (a,b) => { Console.WriteLine(a + b); };
2 t(2, 3);

 

三、Func<T>

Func<T>委托始终都会有返回值,支持协变  返回值的类型是参数中最后一个,可以传入一个参数,也可以最多传入16个参数,但可以传入最多16个参数,两个参数一个返回值的声明原型为:

1 public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2);

用法如下:

复制代码
1 public bool Compare(int a, int b)
2 {
3     return a > b;
4 }
5 
6 Func<int, int, bool> t = new Func<int, int, bool>(Compare);//传入两个int参数,返回bool值
7 bool result = t(2, 3);
复制代码

同样也可以直接用Lambd表达式直接把方法定义在委托中,代码如下:

1 Func<int, int, bool> t = (a, b) => { return a > b; };
2 bool result = t(2, 3);

 

四 、Predicate<T>

Predicate<T>委托表示定义一组条件并确定指定对象是否符合这些条件的方法,返回值始终为bool类型,支持逆变 声明原型为:

1 public delegate bool Predicate<in T>(T obj);

 用法如下:

复制代码
1 public bool Match(int val)
2 {
3     return val > 60;
4 }
5 
6 Predicate<int> t = new Predicate<int>(Match);   //定义一个比较委托
7 int[] arr = { 13, 45, 26, 98, 3, 56, 72, 24 };            
8 int first = Array.Find(arr, t);                 //找到数组中大于60的第一个元素
复制代码

同样也可以直接用Lambd表达式直接把方法定义在委托中,代码如下:

1 Predicate<int> t = val => { return val > 60;};   //定义一个比较委托
2 int[] arr = { 13, 45, 26, 98, 3, 56, 72, 24 };            
3 int first = Array.Find(arr, t);                  //找到数组中大于60的第一个元素

 

泛型和可空引用类型特性

 

T=引用类型 => T?= string?
T=值类型 => T?= int
T=可空引用类型 => T?= string?
T=可空值类型 => T?= int?

 

对于返回值,T?等于[MaybeNull]T;对于参数值,T?等价于[AllowNull]T。有关更多信息,请参阅语言参考中关于属性的空状态分析的文章。泛型约束class标识不可为空的引用类型,class?表示可为空的引用类型

 

 public class  Node<T> where T:notnull
    {
        private T item;
        private Node<T>? next;
}

 

 

 

总结:

  • 如果要委托的方法没有参数也没有返回值就想到Action
  • 有参数但没有返回值就想到Action<T>
  • 无参数有返回值、有参数且有返回值就想到Func<T>
  • 有bool类型的返回值,多用在比较器的方法,要委托这个方法就想到用Predicate<T>

详细内容查看:https://www.cnblogs.com/cdaniu/p/15340355.html

 

posted @ 2021-09-23 17:28  小林野夫  阅读(104)  评论(0编辑  收藏  举报
原文链接:https://www.cnblogs.com/cdaniu/