C# 委托,泛型委托,匿名委托,lambda表达式

2023-05-16

一、泛型的定义及作用

  泛型(generic)是C# 2.0推出的新语法,它是专门为处理多段代码在不同的数据类型上执行相同的指令的情况而设计的。比如说编程时,碰到功能非常相似的模块,只是它们所处理的数据类型不同,然而我们却需要写不同的方法来实现它,很明显,这加大了我们的工作量,也很乏味。有没有什么办法能够解决这个问题呢?它就是泛型了,可以让多个类型共享一组代码。通过压栈例子可以更清楚的了解泛型


    class IntStack
    {int[] arr;
        public void push(int x) { ...}; //将int类型的值压栈
    }

    class FloStack
    {float[] arr;
        public void push(float x) { ...};//将float类型的值压栈
    }  

  这两个类功能一样,只是操作的数据类型不同,并且如果需要新类型(double、string)等时,又需要进行重复的操作,下面介绍怎么通过泛型解决这个问题

二、泛型的使用


class MyStack <T>
    {
        T[] arr;
        public void push(T x) { ...};
    }  

  创建泛型类时,先在类名后面添加<T>,并将类型占位符T替代int或float。 由尖括号和T构成的字符串表明T是类型的占位符(不一定是字母T,也可以是其他标识符)。

  泛型类型不是类型,而是类型的模板,就好比类型不是对象而是对象的模板一样。

  C#提供了五种泛型:类、结构、接口、委托和方法。前面四个是类型,而方法是成员。还是很迷吧,接下来对五种泛型分别进行讲解。

  1、泛型类

  由于泛型类不是实际的类,而是类的模板,所以我们必须先从它们构造实际的类类型,然后创建这个构造后的类类型的实例。

从泛型类型创建实例的过程是:a:声明泛型类型     b:通过提供真实类型创建构造类型    c: 从构造类型创建实例    下面的例子有助于理解


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace generic
{

    class Test<T1, T2>//构造泛型类,T1,T2为类型参数
    {
        public T1 var1;
        public T2 var2;
        public void print()
        {
            Console.WriteLine("var1:{0}    var2:{1}", var1 , var2);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            //两种实例化的方式,效果相同
            Test<int,string> first = new Test<int, string>();//int和string为类型实参,分别对应T1,T2
            var second = new Test<string, int>();

            first.var1 = 123; //first中,var1只能为int类型,当然也可以通过强制类型转换成其他的类型了
            first.var2 = "Good Luck!";  //只能为string类型,同上
            first.print();

            second.var1 = "hello world";
            second.var2 = 345;
            second.print();
            Console.ReadKey();
        }
    }
}  

  

  同一个泛型可以构建出很多不同的类型,互不干扰,每一个都有独立的类类型,就好像有独立的非泛型类声明一样

  

  2、泛型结构


struct MyStruct<T>
{
    public T val;
}  

  和泛型类类似,不过多叙述

  3、泛型委托

  泛型委托和非泛型委托非常相似,不过类型参数决定了它能接受什么方法


delegate S MyDel <T, S>(T value);
//S为委托返回类型,T为委托参数类型  

  4、泛型接口


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace generic
{
    interface Test<T>
    {
        void Print(T value);
    }

    /*
     * 泛型的接口实现必须唯一,必须保证类型参数组合不会再类型中产生两个重复的接口
     * class Simple<S> : Test<int>, Test<S> 是错误的,因为S有可能是int类型
     * 你也可以再非泛型类型中实现泛型接口
     * class Simple : Test<int>, Test<string>
     */
    class Simple<S> : Test<S>
    {
        public void Print(S value)
        {
            Console.WriteLine("value is {0}",value);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var IntSimp = new Simple<int>();
            var StrSimp = new Simple<string>();

            IntSimp.Print(123);
            StrSimp.Print("hello world");
            Console.ReadKey();
        }
    }
}  

  注:泛型接口的名字不会和非泛型冲突,我们可以在前面的代码中声明一个Test的非泛型接口,程序能正常执行

  5、泛型方法

  泛型方法有两个参数列表,封闭在圆括号内的方法参数列表和封闭在尖括号内的类型参数列表,如下所示  


public void Print<S,T> (S val1, T val2)
{
    ...
}  

三、类型参数的约束(constraint)

  顾名思义,即对类型参数进行约束,让编译器知道参数可以接受哪些类型,只有符合约束的类型才能替代给定的类型参数,来产生构造类型

对此,可以使用where子句,其语法为:  where  TypeParam : constraint, constraint ... 

一个where对应一个类型参数,如果类型参数有多个约束,则用逗号分隔


class Test<T,S>
    where T : IComparable
    where S : new()  
  {...}  
  //不理解没关系,下面会讲
    //注意两个where之间没有任何符号分隔,可以以任何次序列出  

  对于约束,有五种约束类型

      

 where子句的约束必须有特定的顺序

  a: 最多只能有一个主约束,如果有则必须放在第一位,主约束可以为结构、类或基类名

  b: 可以有任意多的接口名约束,其类型为接口名称

  c: 如果存在构造函数约束,则必须放在最后面,构造函数约束即为 new()

四、协变和逆变  

  “协变”是指能够使用与原始指定的派生类型相比,派生程度更大的类型。 

  “逆变”则是指能够使用派生程度更小的类型。 

  可以显式使用out关键字指定类型参数的协变,用in关键字指定类型参数的逆变


interface IItf<out T>{...}
delegate void Test<in T>(T a);  

  显式变化使用in和out关键字只适合于委托和接口,不适用于类、结构和方法

/**********************************************C#根据通过泛型指定方法的返回值类型

在编写程序的时候经常会碰到同一个方法有多个重载,其传入传出参数差不多,只是类型不一致。如下所示:

        public double GetData(){    }
 
        public string GetData(){    }
 
        public int GetData() {    }
        对方法进行重载往往显得程序臃肿,那么有没有更好的方法解决呢?

        这里我们采用泛型解决,具体代码实现如下:

        public static T GetData<T>()
        {
            Type t = typeof(T);
            if(t == typeof(Double))
            {
                double result = 1.00009;
                return (T)(Object)result;
            }
            else if(t == typeof(string))
            {
                string result = "这是string类型";
                return (T)(Object)result;
            }
            return default(T);
        }
这是测试方法和测试结果

        public static void Main(string[] args)
        {
            string result1 = GetData<string>();
            Console.WriteLine(result1); //这是string类型
 
            double result2 = GetData<double>();
            Console.WriteLine(result2); //1.00009
            Console.ReadKey();
        }
          

        当然这个方法除了解决实体参数作为泛型返回的问题外,另一个非常棒的用处是:在调用方法时指定返回值类型,例如Newtonsoft.Json中json对象的反序列化就是这样用的。

List<double> result = Newtonsoft.Json.JsonConvert.DeserializeObject<List<double>>(jsonStr);
/**************************************************c# 带返回值的action_C# 委托和匿名函数大总结

一、匿名函数
匿名函数是一个“内联”语句或表达式,可在需要委托类型的任何地方使用。 可以使用匿名函数来初始化命名委托,或传递命名委托(而不是命名委托类型)作为方法参数。

C#中有两种匿名函数:Lambda 表达式 和 匿名方法。

二、C#中委托的发展历程
C# 1.0 中,您通过使用在代码中其他位置定义的方法显式初始化委托来创建委托的实例。

C# 2.0 引入了匿名方法的概念,作为一种编写可在委托调用中执行的未命名内联语句块的方式。

C# 3.0 引入了 Lambda 表达式,这种表达式与匿名方法的概念类似,但更具表现力并且更简练。这两个功能统称为“匿名函数”。通常,针对 .NET Framework 版本 3.5 及更高版本的应用程序应使用 Lambda 表达式。

测试代码如下:

    public class HistoryDelegate
    {
        private delegate void Delegate1(string str_);
        private void OnDelegate1(string str1_)
        {
            Console.WriteLine($"OnDelegate1: {str1_}");
        }
 
        public void OnTest()
        {
            //C# 1.0
            Delegate1 d1 = new Delegate1(OnDelegate1);
            d1("d1");
            //C# 2.0
            Delegate1 d2 = delegate (string str) { Console.WriteLine($"{str}"); };
            d2("d2");
            //C# 3.0
            Delegate1 d3 = (x) => { Console.WriteLine($"{x}"); };
            d3("d3");
        }
    }
三、C#内置泛型委托
Action 委托:

Action是无返回值的泛型委托。

Action 表示无参,无返回值的委托

Action<int,string> 表示有传入参数int,string无返回值的委托

Action<int,string,bool> 表示有传入参数int,string,bool无返回值的委托

Action<int,int,int,int> 表示有传入4个int型参数,无返回值的委托

Action至少0个参数,至多16个参数,无返回值。

测试代码如下:

    public class TestAction
    {
        private void OnAction1(string str_)
        {
            Console.WriteLine($"OnAction1:{str_}");
        }
        private void OnAction2(int index_, string str_) => Console.WriteLine($"OnAction1:{index_}/{str_}");
 
        public void OnTest()
        {
            Action<string> action1 = new Action<string>(OnAction1);
            action1("action1");
            Action<int, string> action2 = new Action<int, string>(OnAction2);
            action2(2, "action2");
        }
    }
Func委托

Func是有返回值的泛型委托,<>中,最后一个类型为返回值类型。

Func<int> 表示无参,返回值为int的委托

Func<object,string,int> 表示传入参数为object, string 返回值为int的委托

Func<object,string,int> 表示传入参数为object, string 返回值为int的委托

Func<T1,T2,,T3,int> 表示传入参数为T1,T2,,T3(泛型)返回值为int的委托

Func至少0个参数,至多16个参数,根据返回值泛型返回。必须有返回值,不可void

测试代码如下:

    public class TestFunc
    {
        private string OnFunc1(int index_, string str_)
        {
            Console.WriteLine($"OnFunc1:{index_}/{str_}");
 
            return str_;
        }
        private int OnFunc2(string str1, string str2_)
        {
            Console.WriteLine($"OnFunc2:{str1}/{str2_}");
            return 1;
        }
        private string TestFunc1<T1, T2>(Func<T1, T2, string> func_, T1 index_, T2 str_)
        {
            return func_(index_, str_);
        }
        public void OnTest()
        {
            Console.WriteLine(TestFunc1<int, string>(OnFunc1, 1, "index"));
 
            Func<string, string, int> func2 = new Func<string, string, int>(OnFunc2);
            Console.WriteLine(func2("name1", "name2"));
        }
    }
Predicate委托

Predicate 是返回bool型的泛型委托

Predicate<int> 表示传入参数为int 返回bool的委托

Predicate有且只有一个参数,返回值固定为bool

测试代码如下:

    public class TestPredicate
    {
        private bool OnPredicate1(int index_)
        {
            if (index_ > 0)
            {
                return true;
            }
            else
            {
                return false;
            }
        }
 
        private bool TestPredicate1<T>(Predicate<T> predicate1, T index_)
        {
            return predicate1(index_);
        }
        public void OnTest()
        {
            Console.WriteLine(TestPredicate1(OnPredicate1, 0));
        }
    }
总结:

委托类似于 C++ 函数指针,但它们是类型安全的。

委托允许将方法作为参数进行传递。

委托可用于定义回调方法。

委托可以链接在一起;例如,可以对一个事件调用多个方法。

方法不必与委托签名完全匹配。

Delegate至少0个参数,至多32个参数,可以无返回值,也可以指定返回值类型

Func可以接受0个至16个传入参数,必须具有返回值

Action可以接受0个至16个传入参数,无返回值

Predicate只能接受一个传入参数,返回值为bool类型

四、优化技巧
1. 尽可能避免匿名函数引用外部变量,让其可被静态化

2. 搞清楚哪些变量被匿名函数引用了,防止内存泄漏

3. 尽量把被引用的变量声明放在后面,用变量复制来延迟匿名函数创建

//错误的写法

void Error1() 
{
    var printList = new List<Action>();
    for ( int i = 0; i < 10; ++i ) {
        // 循环里面一直在构造匿名函数对象,分配大量的内存
        printList.Add( () => Debug.Log( i ) );
    }
    for ( int j = 0; j < printList.Count; ++j ) {
        printList[ j ](); // 结果总是输出10
    }
}
 
void Error2() 
{
    var list = new List<int>();
    list.Add( 1 );
    list.Add( 2 );
 
    int id = 0;
 
    if ( id == 5 ) 
    {  
        // 假如满足几率很小
        // 表面上看,匿名函数对象在此处构造
        // 但实际上,匿名对象在id声明处就已经提前构造好了
        // 这样会 100% 造成内存分配
        list.Find( value => value == id );
    }
}
//正确的写法

void Error1() 
{
    var printList = new List<Action>();
    for ( int i = 0; i < 10; ++i ) 
    {
        // 为了避免循环变量被引用
        // 复制i到局部变量,让其被匿名函数引用
        var _i = i;
        printList.Add( () => Debug.Log( _i ) );
    }
    // 结果虽然正确了,但实际编码中,还是要避免循环中构造匿名函数
    // ...
}
 
void Error2() 
{
    var list = new List<int>();
    // ...
    int id = 0;
    if ( id == 5 ) 
    {
        // 同理,这样匿名函数构造位置延迟到了条件表达式体内
        // 消除多数时候的内存分配操作
        var _id = id;
        list.Find( value => value == _id );
    }
}
五、常用技巧写法
    public class TestProperty
    {
        //定义只读属性
        public string Name => "Hello";
        private string m_name1 = "World";
        public string Name1 => m_name1;
 
        //给属性定义默认值
        public int Age { get; set; } = 18;
        private int m_age1 = 18;
        public int Age1
        {
            get { return m_age1; }
            set { m_age1 = value; }
        }
        private Predicate<string> onRule;
 
        public void OnTest()
        {
            //匿名函数的省略写法:可以省略掉,参数列表括号,函数体括号,返回语句
            List<string> list = new List<string>() { "aaa", "bbb", "name" };
            string result1 = list.Find(x => x == "name");
            string result2 = list.Find((x) => { return x == "ccc"; });
            if (null == result2)
            {
                Console.WriteLine("not find ccc");
            }
            if (null == onRule)
            {
                onRule = OnRule;
                string reslut3 = list.Find(onRule);
            }
 
        }
 
        private bool OnRule(string str_)
        {
            if (str_ == "aaa")
            {
                Console.WriteLine("OnRule find aaa");
                return true;
            }
            else
            {
                Console.WriteLine("OnRule not find aaa");
                return false;
            }
        }
    }
/***************************************

C# 委托,泛型委托,匿名委托,lambda表达式

1、为什么要使用委托
将一个方法作为参数传递给另一个方法

2、委托概念
声明:

public delegate int DelegateName(int a, int b);
1
声明一个委托类型,可以用访问修饰符修饰,delegate关键字,有返回值和参数

委托所指向的函数必须跟委托具有相同的签名,即相同的参数个数,相同的参数类型和相同的返回值类型

创建委托实例写法:
委托 委托名=new 委托(会调用的方法名)
或者:
委托 委托名 =会调用的方法名

3、泛型委托
声明:

public delegate T 委托名<T>(T a, T b);
1
我们每次要使用一个委托时,都需要先声明这个委托类,规定参数和返回值类型,然后才能实例化、调用。为了简化这个过程, .NET 框架为我们封装了三个泛型委托类,它们分别是Action,Func和Predicate。因此大部分情况下我们不必再声明委托,可以拿来直接实例化使用,方便了我们的日常Coding。

1). Action

public delegate void Action<T>(T obj1,Tobj2,...);
1
Action是无返回值的泛型委托

Action表示无参,无返回值的委托

Action<int,string> 表示有传入参数int,string无返回值的委托

Action<int,string,bool> 表示有传入参数int,string,bool无返回值的委托

Action<int,int,int,int> 表示有传入4个int型参数,无返回值的委托

Action至少0个参数,至多16个参数,无返回值。

创建委托实例写法:
Action<参数1, 参数2> 委托名= ((参数1,参数2) => {无返回值的方法体 });

2). Func

public delegate<T> Func<T>(T obj1,Tobj2,...);
1
Func是有返回值的泛型委托

Func<int> 表示无参,返回值为int的委托

Func<object,string,int> 表示传入参数为object, string 返回值为int的委托

Func<T1,T2,T3,int> 表示传入参数为T1,T2,T3(泛型)返回值为int的委托

Func至少0个参数,至多16个参数,根据返回值泛型返回。必须有返回值,不可void

创建委托实例写法:
Func<参数1, 参数2, 返回值> 委托名= ((参数1,参数2) => {带返回值的方法体 });

3). Predicate

  public delegate bool Predicate<T>(T obj);
1
这个委托只能传入一个参数,并且如参的类型由使用者自行提供,返回一个bool类型的值。

Predicate 是返回bool型的泛型委托

Predicate<int> 表示传入参数为int 返回bool的委托

Predicate有且只有一个参数,返回值固定为bool

创建委托实例写法:
Predicate<参数1> 委托名= ((参数1) => {带返回值的方法体 });

4、匿名函数(没有函数名,直接方法体)
本来呢委托定义(声明)好之后,还得再单独定义委托需要使用的方法。不如说你定义了一个计算器的委托, 你还需要重新写计算加减乘除的方法了来供计算器委托来使用。这时候我们就想到了匿名方法。用匿名方法就不需要再单独写加减乘除这些方法了,只需要在匿名方法的方法体内实现这些逻辑就好了。

例子如下:


        delegate int calculator(int x, int y); //委托类型

        static void Main(string[] args)
        {
            //创建委托对象(确定与哪些方法进行绑定),委托实例名=new 委托名(某个类的方法)本例与加法向绑定
            calculator Adding =delegate( int x, int y)
            {
               return x+y;
            };


            calculator Moveing = delegate(int x, int y)
            {
                return x - y;
            };


            calculator Multiply = delegate(int x, int y)
            {
                return x * y;
            };


            calculator Divide = delegate(int x, int y)
            {
                return x / y;
            };
            Adding(4, 4);//8
            Moveing(4, 4);//0
            Multiply(4, 4);//16
            Divide(4, 4);//1

        }

创建委托实例写法:
委托 委托名=delegate(参数){会调用的方法体};

5、 Lambda表达式
定义:"Lambda表达式"是一个匿名函数,是一种高效的类似于函数式编程的表达式

好处:Lambda简化了匿名委托的使用,减少开发中需要编写的代码量。

具体内容:它可以包含表达式和语句,并且可用于创建委托或表达式目录树类型,支持带有可绑定到委托或表达式树的输入参数的内联表达式。

写法:所有Lambda表达式都使用Lambda运算符=>,该运算符读作"goes to"。Lambda运算符的左边是输入参数(如果有),右边是表达式或语句块。Lambda表达式x => x * x读作"x goes to x times x"。

创建委托实例写法:
委托 委托名=((参数1,。。参数n)=>{会调用的方法体});

六、调用委托实例通用写法:
1.委托实例.Invoke(参数列表)

2.委托实例(参数列表)

3.this.Invoke(委托实例, new object[] { 参数列表});
/************************************使用Func<T1, T2, TResult> 委托返回匿名对象

Func<T1, T2, TResult> 委托

封装一个具有两个参数并返回 TResult 参数指定的类型值的方法。

语法


  
  1. public delegate TResult Func<in T1, in T2, out TResult>(
  2. T1 arg1,
  3. T2 arg2
  4. )

  
  1. 类型参数
  2. in T1
  3. 此委托封装的方法的第一个参数类型。
  4. 该类型参数是逆变。即可以使用指定的类型或派生程度更低的类型。有关协变和逆变的更多信息,请参见泛型中的协变和逆变。
  5. in T2
  6. 此委托封装的方法的第二个参数类型。
  7. out TResult
  8. 此委托封装的方法的返回值类型。
  9. 该类型参数是协变。即可以使用指定的类型或派生程度更高的类型。有关协变和逆变的更多信息,请参见泛型中的协变和逆变。
  10. 参数
  11. arg1类型:T1
  12. 此委托封装的方法的第一个参数。
  13. arg2类型:T2
  14. 此委托封装的方法的第二个参数。
  15. 返回值
  16. 类型:TResult
  17. 此委托封装的方法的返回值。

备注

可以使用此委托表示一种能以参数形式传递的方法,而不用显式声明自定义委托。封装的方法必须与此委托定义的方法签名相对应。也就是说,封装的方法必须具有两个均通过值传递给它的参数,并且必须返回值。


  
  1. 若要引用具有两个参数并返回 void 的方法(或者要在 Visual Basic 中引用被声明为 Sub 而不是被声明为 Function 的方法),请改用泛型 Action<T1, T2> 委托。

  在使用 Func<T1, T2, TResult> 委托时,不必显式定义一个封装具有两个参数的方法的委托。例如,以下代码显式声明了一个名为 ExtractMethod 的委托,并将对 ExtractWords 方法的引用分配给其委托实例。

示例

下面的示例演示如何声明和使用 Func<T1, T2, TResult> 委托。此示例声明一个 Func<T1, T2, TResult> 变量,并将其分配给一个采用 String 值和 Int32 值作为参数的 lambda 表达式。如果 String 参数的长度等于 Int32 参数的值,则此 lambda 表达式将返回 true。随后在查询中使用封装此方法的委托来筛选字符串数组中的字符串。


  
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. public class Func3Example
  5. {
  6. public static void Main()
  7. {
  8. Func<String, int, bool> predicate = (str, index) => str.Length == index;
  9. String[] words = { "orange", "apple", "Article", "elephant", "star", "and" };
  10. IEnumerable<String> aWords = words.Where(predicate).Select(str => str);
  11. foreach (String word in aWords)
  12. Console.WriteLine(word);
  13. }
  14. }

示例

了解完这些以后,我们来看看它的应用。
      不知道童鞋们有没有遇到这样的问题,在读取数据访问层中数据集合时,发现该方法需要返回的结果中包括对象及其外键对象时,又不想添加一个实体类来封装它,那么有什么别的好办法吗?也许您会选择用动态对象(dynamic关键字),不错,这个这确实可以解决这个问题,但是有一个弊端,且不论动态对象在运行时编译,在编写程序时,它存在一个很不方便的体验,它不能点(.)出它的属性,不免产生意外的拼写错误或是寻找-复制-黏贴的麻烦。

那么怎么办呢?我们可以利用Func<T1, T2, TResult> 委托来帮助我们实现。


  
  1. public IEnumerable<TResult> GetAllUser<TResult>(Func<User, Person, TResult> itemFactory)
  2. {
  3. var results = from u in User
  4. join p in Person on u.Id equals p.uId
  5. select new { User = u, Person = p };
  6. IList<TResult> resultItems = new List<TResult>();
  7. foreach (var item in results.ToList())
  8. {
  9. resultItems.Add(itemFactory(item.User, item.Person));
  10. }
  11. return resultItems;
  12. }

数据访问层


  
  1. var query = userDao.GetAllUser((u, p) =>
  2. {
  3. return new { u.Id, u.LoginName, pId = p.Id, p.Name };
  4. });

表示层调取

/*************************************

C# 有返回类型的内置委托—Func

2.1 初识Func

MSDN给出的定义: 封装一个具有一个参数并返回  TResult 参数指定的类型值的方法

  此委托的定义如下:


public delegate TResult Func<in T, out TResult>(T arg)  

  (1)in T :此委托封装的方法的参数类型。

  (2)out TResult :此委托封装的方法的返回值类型。

  可以使用此委托表示一种能以参数形式传递的方法,而不用显式声明自定义委托。封装的方法必须与此委托定义的方法签名相对应。也就是说,封装的方法必须具有一个通过值传递给它的参数,并且必须返回值。

  2.1.1 没有Func时的使用

 View Code

  2.1.2 有了Func后的使用

 View Code

  当然,我们还可以借助匿名方法更加便捷地使用:

 View Code

  可以清楚地看出,现在使用 Func 委托时,不必显式定义一个新委托并将命名方法分配给该委托。

2.2 深入Func

  2.2.1 用法先行:爽一下

  我们已经知道Func委托是带指定返回值类型的委托,那么我们来看看在实际开发场景的一幕。还是以刚刚那个数据集合PersonList为例,在很多时候我们需要对从数据库中读取的数据集合进行二次筛选,这时我们可以使用List集合的Select方法,我们将一个Func委托实例作为方法参数传递给Select方法,就可以返回一个符合我们指定条件的新数据集合。

  (1)先来看看Select方法的定义:


        //
        // 摘要:
        //     将序列中的每个元素投影到新表中。
        //
        // 参数:
        //   source:
        //     一个值序列,要对该序列调用转换函数。
        //
        //   selector:
        //     应用于每个元素的转换函数。
        //
        // 类型参数:
        //   TSource:
        //     source 中的元素的类型。
        //
        //   TResult:
        //     selector 返回的值的类型。
        //
        // 返回结果:
        //     一个 System.Collections.Generic.IEnumerable<T>,其元素为对 source 的每个元素调用转换函数的结果。
        //
        // 异常:
        //   System.ArgumentNullException:
        //     source 或 selector 为 null。
        public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector);  

  可以看出,Select方法中的参数采用了Func泛型委托,根据泛型委托的定义TSource和TResult分别代表要传入的数据类型以及要返回的数据类型。

  (2)再来看看如何在程序中使用Func委托:

  首先定义一个与源数据类型不同的新数据类型作为返回值类型:


    public class LitePerson
    {
        public string Name { get; set; }
    }  

  ①标准定义版:


            List<Person> personList = GetPersonList();
            
            IEnumerable<LitePerson> litePersonList = personList.Select<Person, LitePerson>(
                new Func<Person, LitePerson>
                (
                    delegate(Person p)
                    {
                        return new LitePerson() { Name = p.Name };
                    }
                )
            );      

  ②嘻哈简化版:借助编译器提供的自动识别,简化我们的代码


            IEnumerable<LitePerson> litePersonList = personList.Select(
                delegate(Person p)
                {
                    return new LitePerson() { Name = p.Name };
                }
            );  

  ③绝逼懒人版:借助匿名类和泛型可以大大简化我们的代码


            var liteList = personList.Select(delegate(Person p)
            {
                return new { Name = p.Name, AddDate = DateTime.Now };
            });  

  (3)调试运行可以得到以下结果:

  2.2.2 原理为王:探一次

  (1)通过Reflector反编译,我们再来看看编译器帮我们生成的东东:

  (2)看看自动生成的委托和方法的定义:

  相信经过上节Action的详细分析,这里大家应该也可以举一反三了解编译器帮我们到底做了什么事儿了,这里我就不再赘述了,后面也不会再赘述此方面的东东(为了节省页面大小)。

  当然,和Action类似,.NET基类库为我们也提供了多达16个输入参数的Func委托,但是,输出参数却只有1个。

/*****************************************************

C#委托、Action、Func、lambda表达式

一、委托
注意:在使用委托的时候,
1、在A要委托B的时候,在B中声明一个委托,在A中赋值委托B,当B中的条件触发时,回调A中的方法
举例:当在一个界面中,在点击滑动列表中的某一项是,在界面中展示该项目详细信息时,可以在滑动列表中创建委托,在展示界面中对滑动列表中的委托进行注册,这样当在点击滑动列表项目的时候,调用委托,传送数据,展示界面数据刷新

1、普通委托不带参数

public class TestAction : MonoBehaviour {

public delegate void TestDelegate();
public TestDelegate test;
void Start () {
    test = ShowTest;
    test();
}

void ShowTest() {
    Debug.Log("普通委托。。。");
}

}

2、普通委托带参数

public class TestAction : MonoBehaviour {

public delegate void TestDelegate(int temp);
public TestDelegate test;
void Start () {
    test = ShowTest;
    test(2);
}

void ShowTest(int temp) {
    Debug.Log("带参数委托委托。。。" + temp);
}

}

3、C# Action 委托,是不带返回值的委托

public class TestAction : MonoBehaviour {

void Start () {
    Action<string> testAction = ShowTest;
    testAction("C# Action 测试!");
}

void ShowTest(string temp) {
    Debug.Log(temp);
}

}

4、C# Func 委托

public class TestAction : MonoBehaviour {

void Start () {
    //Func<string,string>第一个参数是输入类型,第二个返回类型Func<T,TResult>
    Func<string,string> testFunc = ShowTest;
    Debug.Log(testFunc("C# Func 测试!"));
}

String ShowTest(string temp) {
    return temp;
}

}

public class TestAction : MonoBehaviour {

void Start () {
    //如果是一个类型Func<string>,则表示返回类型为string
    Func<string> testFunc = ShowTest;
    Debug.Log(testFunc());
}

String ShowTest() {
    return "C# Func 测试,单个参数是返回类型!";
}

}

5、lambda表达式
注意:主要用于简化函数,代码看起来简洁明了

public class TestAction : MonoBehaviour {

void Start () {
    //第一个参数是传入参数类型,第二个返回委托类型
    Func<int, string> testFunc = test => test + 10 + "--返回类型为string"; 
    Debug.Log(testFunc(2));
}

int ShowTest(int temp) {
    return temp;
}

}

lambda整体写法

public class TestAction : MonoBehaviour {

void Start () {
    Func<int, int, bool> test = (p, k) => {
        if (p + k == 10)
        {
            return true;
        }
        else {
            return false;
        }
    };
    Debug.Log("lambda 表达式,返回值测试 " + test(8,8));
}

}
 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C# 委托,泛型委托,匿名委托,lambda表达式 的相关文章

  • PELCO-D协议 要点整理

    消息格式 Byte 1Byte 2Byte 3Byte 4Byte 5Byte 6Byte 7Sync ByteAddressCommand 1Command 2Data 1Data 2Checksum The synchronizatio
  • GTEST/GMOCK介绍与实战:Gtest Sample9

    文章目录 1 简介2 用法 1 简介 示例 9显示了使用侦听器API修改谷歌Test的控制台输出和使用其反射API来检查测试结果 2 用法 span class token comment This sample shows how to
  • Gtest输出单元测试报告和输出覆盖率报告

    文章目录 1 要求2 生成gtest测试报告3 生成gtest覆盖率报告 1 要求 编译工具 xff1a 选择Cmake xff0c 单元测试使用Gtest 2 生成gtest测试报告 gtest本身仅能输出xml或者json格式的测试报告
  • GTEST/GMOCK介绍与实战:Gtest Sample10

    文章目录 1 简介2 用法 1 简介 示例 10展示了如何使用侦听器API来实现基本内存泄漏检查 2 用法 span class token comment This sample shows how to use Google Test
  • Bitbake与Yocto

    文章目录 一 Bitbake二 Yocto 一 Bitbake xff08 1 xff09 使用教程可以参考 xff1a BitBake 实用指南 xff0c 大部分步骤跟着操作即可了解bitbake的工作流程 xff1b 他主要参考和翻译
  • 随机漫步

    span class token keyword import span numpy span class token keyword as span np span class token keyword import span rand
  • UTC时间和PTP精确时间协议

    文章目录 一 GMT二 UTC三 GMT vs UTC四 C 43 43 获得当前的UTC时间 一 GMT GMT xff08 Greenwich Mean Time xff09 xff0c 格林威治平时 xff08 也称格林威治时间 xf
  • AutoSar系列之:AutoSar发展

    文章目录 一 Autosar成员二 Autosar历史发展三 使用Autosar前的状态1 原始状态2 进阶状态 四 使用Autosar后的状态1 软硬件隔离2 Autosar优势 一 Autosar成员 二 Autosar历史发展 三 使
  • AutoSar系列之:AutoSar概述

    文章目录 一 Autosar是什么二 架构 一 Autosar是什么 RTE xff1a 用与传递应用层软件和基础软件从之间的信号的 xff1b 隔离应用软件层和基础软件层 xff1b 其中一个层修改了 xff0c 不会影响另外一个层 xf
  • Autosar系列之Appl概述

    文章目录 一 Appl的组成1 SWC通信2 SWC分配 一 Appl的组成 SWC xff1a 应用软件组件 Autosar接口 xff1a SWC之间连接的端口 Runnable xff1a 可运行实体 xff0c SWC里面的一些函数
  • Autosar系列之SWC类型

    文章目录 一 原子级SWC二 集合级SWC三 特殊的SWC 一 原子级SWC 含义 xff1a 不可拆解的SWC 二 集合级SWC eg xff1a 将相似的功能放在一起 三 特殊的SWC IoHwAb xff0c Cdd 在原有的Auto
  • 汽车操作系统

    文章目录 一 汽车控制器类型二 Hypervisor三 QNX Linux Andorid四 Automotive Grade Linux 系统 xff08 AGL xff09 1 介绍2 IVI市场现状3 系统构建 xff08 1 xff
  • Autosar系列之Ports类型

    文章目录 一 接口二 接口类型三 S R接口四 C S 接口 一 接口 接口是连接2个SWC通信的 二 接口类型 三 S R接口 发送 接受数据传输接口 一般通过全局变量才传递 四 C S 接口 客户 服务接口 xff1b 通过函数Runn
  • Autosar系列之Runnable可运行实体

    文章目录 一 RUnnable Entity 一 RUnnable Entity 可运行实体 xff0c 其实就是 C文件内的函数而已 一个SWC可以包含多个Runnable Entity xff0c 就是一个 C文件中可以包含多个函数 x
  • Autosar系列之RTE

    文章目录 一 RTE二 RTE功能 一 RTE RTE Run TIme Environment 是Autosar体系结构的核心 RTE是Autosar软件架构中 xff0c 介于应用层和基础软件层之间 xff0c 是Autosar虚拟功能
  • Autosar系列之Autosar应用层整体入门

    文章目录 一 整个功能示意图二 软件组件SWC分类三 SWC组件 xff1a ports1 发送 接收端口Sender Receiver2 客户端 服务端端口Client Server 四 可运行实体Runnables五 BSW1 微控制器
  • ubuntu下mysql数据库的设置

    gt su root gt mysql span class token operator span u root span class token operator span p gt show databases span class
  • Python装饰器Decorators

    文章目录 一 功能二 64 语法糖三 args kwargs四 带参数的装饰器五 类装饰器六 装饰器顺序 一 功能 装饰器本质上是一个 Python 函数或类 xff0c 它可以让其他函数或类在不需要做任何代码修改的前提下增加额外功能 xf
  • Autosar系列之Developer工具

    文章目录 一 什么是DaVinci Developer xff1f 二 DaVinci Developer Workspace三 Software Conponent xff08 SWC xff09 Design 一 什么是DaVinci
  • vscode中调试rust程序

    文章目录 一 vscode运行和调式rust程序二 常见问题1 rust Request textDocument formatting failed 2 cargo命令3 使用rust gdb调试rust程序4 cargo build太慢

随机推荐