C# 委托和匿名函数大总结

8/2/2021 Auther:炉石不传说 View:351

一、匿名函数

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

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;
            }
        }
    }

by 2020-09-27 周日 下午