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;

            }

        }

    }

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,978评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,954评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,623评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,324评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,390评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,741评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,892评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,655评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,104评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,451评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,569评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,254评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,834评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,725评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,950评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,260评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,446评论 2 348