精通C#--委托,事件和Lambda表达式

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/x13262608581/article/details/81269683

在.NET平台下,委托类型用来定义和响应应用程序中的回调。
.NET委托类型是一个类型安全的对象,指向可以以后调用的其它方法。【.NET委托支持多路广播和异步方法调用】

.NET委托类型

非泛型委托

1.
委托是一个类型安全的对象,它指向程序中另一个以后会被调用的方法(或多个方法)。
委托类型包含3个重要的信息:
所调用的方法的名称
方法的参数【可选】
方法的返回值类型【可选】

.NET委托可以指向静态方法,页可以指向实例方法。

一个委托对象被创建并提供上述信息后,可以在运行时动态调用其指向的方法。
.NET Framework中每个委托都被自动赋予同步或异步访问方法的能力。可以直接调用另一个辅助执行线程上的方法。

2.
C#编译器处理委托类型时,先自动产生一个派生自System.MulticastDelegate的密封类。
2.1.Invoke()
以同步方式调用委托对象维护的每个方法
同步Invoke()不能在C#中直接调用
2.2.BeginInvoke()和EndInvoke()
能在第二个执行线程上异步调用当前方法。


// 定义一个委托
// 指向传入两个整数,返回一个整数的方法
public delegate int BinaryOp(int x, int y);

// 这是编译器默认的合成版本
public sealed class BinaryOp : System.MulticastDelegate
{
    public int Invoke(int x, int y);
    public IAsyncResult BeginInvoke(int x, int y, 【委托参数列表】
    AsyncCallback cb, object state);
    public int【委托返回值类型】 EndInvoke(IAsyncResult result);
}

public delegate string MyOtherDelegate(out bool a, ref bool b, int c);

// 编译器默认的合成版本
public sealed class MyOtherDelegate : System.MulticastDelegate
{
    public string【返回值类型】 Invoke(out bool a, ref bool b, int c【参数列表】);
    public IAsyncResult BeginInvoke(out bool a, ref bool b, int c, 【参数列表】
    AsyncCallback cb, object state);
    public string【返回值类型】 EndInvoke(out bool a, ref bool b,【包含out/ref修饰的参数这里也要放上】 IAsyncResult result);
}

// 通用合成类表示
public sealed class DelegateName : System.MulticastDelegate
{
    public delegateReturnValue Invoke(allDelegateInputRefAndOutParams);
    public IAsyncResult BeginInvoke(allDelegateInputRefAndOutParams, AysncCallback cb, object state);
    public delegateReturnValue EndInvoke(allDelegateRefAndOutParams, IAsyncResult result);
}

需要学习的基础设施:
System.MulticastDelegate / System.Delegate

namespace SimpleDelegate
{
    public delegate int BinaryOp(int x, int y);
    public class SimpleMath
    {
        public static int Add(int x, int y)
        {
            return x + y;
        }

        public static int Subtract(int x, int y)
        {
            return x - y;
        }
    }

    class Program
    {
        static void Main()
        {
            Console.WriteLine("***Simple Delegate Example***\n");
            BinaryOp b = new BinaryOp(SimpleMath.Add);
            // b(10, 10)等价与 b.Invoke(10, 10)
            Console.WriteLine("10 + 10 is {0}", b(10, 10));
            Console.ReadLine();
        }

        static void DisplayDelegateInfo(Delegate delObj)
        {
            foreach(Delegate d in delObj.GetInvocationList())
            {
                Console.WriteLine("Method Name:{0}", d.Method);
                Console.WriteLine("Type Name:{0}", d.Target);
            }
        }
    }
}

3.使用委托发送对象状态通知–使用委托定义Car
定义将通知发送给调用者的委托类型
声明Car类中每个委托类型的成员变量
在Car上创建辅助函数使调用者能指定由委托成员变量保存的方法
修改Accelerate()方法以在适当情形下调用委托的调用列表

public class Car
{
    public int CurrentSpeed{get; set;}
    public int MaxSpeed{get; set;}
    public string PetName{get; set;}

    private bool carIsDead;
    public Car()
    {
        MaxSpeed = 100;
    }
    public Car(string name, int maxSp, int currSp)
    {
        CurrentSpeed = currSp;
        MaxSpeed = maxSp;
        PetName = name;
    }

    // 1.定义委托类型
    public delegate void CarEngineHandler(string msgForCaller);
    // 2.定义每个委托类型的成员变量
    private CarEngineHandler listOfHandlers;
    // 3.向调用者添加注册函数
    public void RegisterWithCarEngine(CarEngineHandler methodToCall)
    {
        listOfHandlers = methodToCall;
    }

    public void Accelerate(int delta)
    {
        if(carIsDead)
        {
            if(listOfHandlers != null)
            {
                listOfHandlers("Sorry, this car is dead...");
            }
        }
        else
        {
            CurrentSpeed += delta;
            if(10 == (MaxSpeed - CurrentSpeed)
            && listOfHandlers != null)
            {
                listOfHandlers("Careful buddy! Gonna blow!");
            }

            if(CurrentSpeed >= MaxSpeed)
            {
                CarIsDead = true;
            }
            else
            {
                Console.WriteLine("CurrentSpeed = {0}", CurrentSpeed);
            }
        }
    }
}

class Program
{
    static void Main()
    {
        Console.WriteLine("***Delegates as event enablers***\n");
        Car c1 = new Car("SlugBug", 100, 10);
        c1.RegisterWithCarEngine(new Car.CarEngineHandler(OnCarEngineEvent));
        Console.WriteLine("***Speeding up***");
        for(int i = 0; i < 6; i++)
        {
            c1.Accelerate(20);
        }

        Console.ReadLine();
    }

    public static void OnCarEngineEvent(string msg)
    {
        Console.WriteLine("\n***Message From Car Object***");
        Console.WriteLine("=>{0}", msg);
        Console.WriteLine("*************\n");
    }
}

4.支持多路广播
一个委托对象可以维护一个可调用方法的列表。
给一个委托对象添加多个方法时,重载+=操作符即可。

public class Car
{
    public void RegisterWithCarEngine(CarEngineHandler methodToCall)
    {
        // 等价的表达
        // if(listOfHandlers == null)
        //    listOfHandlers = methodToCall;
        // else
        //    Delegate.Combine(listOfHandlers, methodToCall);
        listOfHandlers += methodToCall;
    }

    ...
}

5.从委托的调用列表移除成员

public class Car
{
    public void UnRegisterWithCarEngine(CarEngineHandler methodToCall)
    {
        listOfHandlers -= methodToCall;
    }
}

6.方法组转换语法

static void Main()
{
    Console.WriteLine("***Delegates as event enablers***\n");
    Car c1 = new Car("SlugBug", 100, 10);
    // 常常使用委托对象只是为了传递作为构造函数参数的方法名称
    // 为了简化操作,C#提供了一种叫方法组转换的方法。
    // 允许我们在调用以委托作为参数的方法时直接提供方法名称【创建委托对象并将方法名称作为参数传递给对象构造函数隐式执行】
    c1.RegisterWithCarEngine(new Car.CarEngineHandler(OnCarEngineEvent));
    Car.CarEngineHandler handler2 = new Car.CarEngineHandler(OnCarEngineEvent2);
    c1.RegisterWithCarEngine(handler2);
    ...
}

// 
static void Main()
{
    ...
    c1.RegisterWithCarEngine(CallMeHere);
    ...
}

static void CallMeHere(string msg)
{
    Console.WriteLine("=>Message from Car:{0}", msg);
}

##泛型委托
1.

namespace GenericDelegate
{
    // 泛型委托
    public delegate void MyGenericDelegate<T>(T arg);
    class Program
    {
        static void Main()
        {
            Console.WriteLine("***Generic Delegates***\n");
            MyGenericDelegate<string> strTarget = new MyGenericDelegate<string>(StringTarget);
            strTarget("Some string data");
            MyGenericDelegate<int> intTarget = new MyGenericDelegate<int>(IntTarget);
            intTarget(9);
            Console.ReadLine();
        }

        static void StringTarget(string arg)
        {
            Console.WriteLine("arg in uppercase is:{0}", arg.ToUpper());
        }

        static void IntTarget(int arg)
        {
            Console.WriteLine("++arg is:{0}", ++arg);
        }
    }
}

2.泛型Action<>和Func<>委托
使用委托在应用中回调时,须:
自定义一个与要指向的方法的格式相匹配的委托
创建自定义委托的实例,将方法名作为构造函数的参数
通过调用委托对象的Invoke()来间接调用该方法

许多情况下,只需要接受一组参数并返回一个值的委托,委托名无关紧要。
这时可使用Action<>和Func<>。

// Action<>委托的一个目标
static void DisplayMessage(string msg, ConsoleColor txtColor, int printCount)
{
    ConsoleColor previous = Console.ForegroundColor;
    Console.ForegroundColor = txtColor;
    for(int i = 0; i < printCount; i++)
    {
        Console.WriteLine(msg);
    }

    Console.ForegroundColor = previous;
}

static void Main(string[] args)
{
    Console.WriteLine("***Fun with Action and Func***");
    // Action<>委托只能指向返回void的方法。
    // 如果要指向具有返回值的方法【又不不想编写自定义委托】,可以使用Func<>
    Action<string, ConsoleColor, int> actionTarget = new Action<string, ConsoleColor, int>(DisplayMessage);
    actionTarget("Action Message!", ConsoleColor.Yellow, 5);
    Console.ReadLine();
}

///////////////////////////////////////////////////////////////////////////////
static int Add(int x, int y)
{
    return x + y;
}

static string SumToString(int x, int y)
{
    return (x+y).ToString();
}

static void Main()
{
    // 最后一个参数固定为调用方法的返回值类型
    Fun<int, int, int> funcTarget = new Func<int, int, int>(Add);
    int result = funcTarget.Invoke(40, 40);
    Console.WriteLine("40+40={0}", result);
    Func<int, int, string> funcTarget2 = new Func<int, int, string>(SumToString);
    string sum = funcTarget2(90, 300);
    Console.WriteLine(sum);
}

C#事件

为了简化自定义方法的构建来为委托调用列表增加和删除方法,C#提供了event关键字。
编译器处理event关键字时,自动提供注册和注销方法以及必要的委托类型成员变量。这些委托成员变量总是声明为私有的。
event节省了完整的委托定义。
1.定义一个事件
定义一个委托类型,它包含在事件触发时将要调用的方法。
通过C# event关键字用相关委托声明这个事件。

public class Car
{
    // 这个委托用来与Car的事件协作
    public delegate void CarEngineHandler(string msg);

    // 这种汽车可以发送这些事件
    public event CarEngineHandler Exploded;
    public event CarEngineHandler AboutToBlow;
    ...
}

public void Accelerate(int delta)
{
    if(carIsDead)
    {
        if(Exploded != null)
        {
            Exploded("Sorry, this car is dead...");
        }
    }
    else
    {
        CurrentSpeed += delta;
        if(10 == MaxSpeed - CurrentSpeed
        && AboutToBlow != null)
        {
            AboutToBlow("Careful buddy! Gonna blow!");
        }

        if(CurrentSpeed >= MaxSpeed)
        {
            carIsDead = true;
        }
        else
        {
            Console.WriteLine("CurrentSpeed = {0}", CurrentSpeed);
        }
    }
}

向调用者发送一个事件,就如通过名称和相关联委托定义的必需参数来指定事件这么简单。

2.
C#事件会扩展为两个隐藏的公共方法。一个带add_前缀,另一个带remove_前缀。

3.监听传入的事件

// NameOfObject.NameOfEvent += new RelatedDelegate(functionToCall);
Car.CarEngineHandler d = new Car.CarEngineHandler(CarExploded);
myCar.Exploded += d;

// NameOfObject.NameOfEvent -= new RelatedDelegate(functionToCall);
myCar.Exploded -= d;

class Program
{
    static void Main()
    {
        Console.WriteLine("***Fun With Events***\n");
        Car c1 = new Car("SlugBug", 100, 10);

        c1.AboutToBlow += new Car.CarEngineHandler(CarIsAlmostDoomed);
        c1.AboutToBlow += new Car.CarEngineHandler(CarAboutToBlow);

        Car.CarEngineHandler d = new Car.CarEngineHandler(CarExploded);
        c1.Exploded += d;
        Console.WriteLine("***Speeding up***");
        for(int i = 0; i < 6; i++)
        {
            c1.Accelerate(20);
        }

        c1.Exploded -= d;
        Console.WriteLine("\n***Speeding up***");
        for(int i = 0; i < 6; i++)
        {
            c1.Accelerate(20);
        }
        Console.ReadLine();
    }
}

public static void CarAboutToBlow(string msg)
{
    Console.WriteLine(msg);
}

public static void CarIsAlmostDoomed(string msg)
{
    Console.WriteLine("=>Critical Message from Car:{0}", msg);
}

public static void CarExploded(string msg)
{
    Console.WriteLine(msg);
}

// 进一步简化事件注册,可以使用方法组转换【在需要传递委托对象的时候,直接传递方法名】
static void Main()
{
    Car c1 = new Car("SlugBug", 100, 10);
    c1.AboutToBlow += CarIsAlmostDoomed;
    c1.AboutToBlow += CarAboutToBlow;
    c1.Exploded += CarExploded;

    ....
}

4.创建自定义的事件参数

public class EventArgs
{
    public static readonly EventArgs Empty;
    public EventArgs();
}

public class CarEventArgs : EventArgs
{
    public readonly string msg;
    public CarEventArgs(string message)
    {
        msg = message;
    }
}

public class Car
{
    public delegate void CarEngineHandler(object sender, CarEventArgs e);
    public void Accelerate(int delta)
    {
        if(carIsDead)
        {
            if(Exploded != null)
            {
                Exploded(this, new CarEventArgs("Sorry, this car is dead..."));
            }
        }
        ...
    }

    public static void CarAboutToBllow(object sender, CarEventArgs e)
    {
        Console.WriteLine("{0} says:{1}", sender, e.msg);
    }
    ...
}

5.泛型EventHandler委托

// T就是自定义的EventArgs类型
// EventHandler<T>
public class Car
{
    // 不再需要一个自定义的委托类型
    // 仅仅适用于 接受object作为参数1,接受EventArgs派生类型作为参数2,返回类型为void的委托
    public event EventHandler<CarEventArgs> Exploded;
    public event EventHandler<CarEventArgs> AboutToBlow;
    ...
}

static void Main()
{
    Console.WriteLine("***Prim and Proper Events***\n");
    Car c1 = new Car("SlugBug", 100, 10);
    c1.AboutToBlow += CarIsAlmostDoomed;
    c1.AboutToBlow += CarAboutToBlow;

    EventHandler<CarEventArgs> d = new EventHandler<CarEventArgs>(CarExploded);
    c1.Exploded += d;
    ...
}

C#匿名方法

1.
可以在事件注册时,直接将一个委托与一段代码关联。
这种代码称为匿名方法。
用委托对象的地方,可以用匿名方法,也可以用Lambda表达式。

class Program
{
    static void Main()
    {
        Console.WriteLine("***Anonymous Methods***\n");
        Car c1 = new Car("SlugBug", 100, 10);
        // 注册事件处理程序作为匿名方法
        c1.AboutToBlow += delegate
        {
            Console.WriteLine("Eek! Going too fast!");
        };

        c1.AboutToBlow += delegate(object sender, CarEventArgs e)
        {
            Console.WriteLine("Message from Car:{0}", e.msg);
        };

        c1.Exploded += delegate(object sender, CarEventArgs e)
        {
            Console.WriteLine("Fatal Message from Car:{0}", e.msg);
        };

        for(int i = 0; i < 6; i++)
        {
            c1.Accelerate(20);
        }
        Console.ReadLine();
    }
}

2.
匿名方法内可以访问匿名方法所在作用域内的本地变量。
这些变量称为匿名方法的外部变量。
匿名方法不能访问定义方法中的ref或out参数
匿名方法中的本地变量不能与外部方法中的本地变量重名
匿名方法可以访问外部类作用域的实例变量或静态变量。
匿名方法内的本地变量可以与外部类的成员变量同名。

Lambda表达式

1.
Lambda表达式只是用更简单的方式来写匿名方法。

public List<T> FindAll(Predicate<T> match);
public delegate bool Predicate<T>(T obj);

class Program
{
    static void Main()
    {
        Console.WriteLine("***Fun With Lambdas***\n");
        TraditionalDelegateSyntax();
        Console.ReadLine();
    }

    static void TraditionalDelegateSyntax()
    {
        List<int> list = new List<int>();
        list.AddRange(new int[]{20, 1, 4, 8, 9, 44});
        // 匿名方法版本
        //List<int> evenNumbers = list.FindAll(
        //delegate(int i)
        //{
        //  return (i%2)==0;
        //});
        // Lambda表达式版本
        // 编译器依据Lambda表达式上下文和底层委托,推断i为int
        // List<int> evenNumbers = list.FindAll(i =>(i%2)==0);
        // 或List<int> evenNumbers = list.FindAll((int i) =>(i%2)==0);
        Predicate<int> callback = new Predicate<int>(IsEvenNumber);
        List<int> evenNumbers = list.FindAll(callback);
        ...
    }

    static bool IsEvenNumber(int i)
    {
        return (i % 2) == 0;
    }
}

Lambda表达式可以应用于任何匿名方法或强类型委托的场合。

2.Lambda表达式形式
ArgumentsToProcess => StatementsToProcessThem
Lambda表达式的参数可以显式和隐式类型化。
参数隐式类型化时,参数类型推断得出。返回值类型由表达式结果推断。

static void LambdaExpressionSyntax()
{
    List<int> list = new List<int>();
    list.AddRange(new int[]{20, 1, 4, 8, 9, 44});
    List<int> evenNumbers = list.FindAll(
    (i)=>
    {
        Console.WriteLine("value of i is currently:{0}", i);
        bool isEven = ((i%2)==0);
        return isEven;
    });
}

3.含多个或零个参数的Lambda表达式

public class SimpleMath
{
    public delegate void MathMessage(string msg, int result);
    private MathMessage mmDelegate;
    public SetMathHandler(MathMessage target)
    {
        mmDelegate = target;
    }
}

class Program
{
    static void Main()
    {
        SimpleMath m = new SimpleMath();
        m.SetMathHandler(
        // 无参版本()
        // 显式版本(string msg, int result)
        (msg, result)=>
        {
            Console.WriteLine("Message:{0}, Result:{1}", msg, result);
        });

        m.Add(10, 10);
    }
}

猜你喜欢

转载自blog.csdn.net/x13262608581/article/details/81269683
今日推荐