现在的位置: 首页 > 综合 > 正文

委托(C# 编程指南)

2018年08月05日 ⁄ 综合 ⁄ 共 15906字 ⁄ 字号 评论关闭

 

委托(C# 编程指南)

委托是一种定义方法签名的类型。当实例化委托时,您可以将其实例与任何具有兼容签名的方法相关联。您可以通过委托实例调用方法。

委托用于将方法作为参数传递给其他方法。事件处理程序就是通过委托调用的方法。您可以创建一个自定义方法,当发生特定事件时某个类(例如
Windows 控件)就可以调用您的方法。
下面的示例演示了一个委托声明:

    public delegate int PerformCalculation(int x, int y);



与委托的签名(由返回类型和参数组成)匹配的任何可访问类或结构中的任何方法都可以分配给该委托。方法可以是静态方法,也可以是实例方法。这样就可以通过编程方式来更改方法调用,还可以向现有类中插入新代码。只要知道委托的签名,就可以分配您自己的方法。

注意注意

在方法重载的上下文中,方法的签名不包括返回值。但在委托的上下文中,签名的确包括返回值。换句话说,方法和委托必须具有相同的返回值。

将方法作为参数进行引用的能力使委托成为定义回调方法的理想选择。例如,可以比较两个对象方法的引用可以被作为参数传递到排序算法中。由于比较代码在一个单独的过程中,因此可通过更常见的方式写入排序算法。

委托概述


委托具有以下特点:

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

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

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

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

  • 方法不必与委托签名完全匹配。有关更多信息,请参见在委托中使用变体(C# 和 Visual
    Basic)

C# 2.0 版引入了匿名方法的概念,此类方法允许将代码块作为参数传递,以代替单独定义的方法。C#
3.0 引入了 Lambda 表达式,利用它们可以更简练地编写内联代码块。
匿名方法和 Lambda 表达式(在某些上下文中)都可编译为委托类型。这些功能统称为匿名函数。有关
lambda 表达式的更多信息,请参见匿名函数(C# 编程指南)

使用委托 

委托是一种安全地封装方法的类型,它与 C 和 C++ 中的函数指针类似。 与 C 中的函数指针不同,委托是面向对象的、类型安全的和保险的。 委托的类型由委托的名称定义。 下面的示例声明了一个名为 Del 的委托,该委托可以封装一个采用字符串作为参数并返回 void 的方法。

public delegate void Del(string message);


构造委托对象时,通常提供委托将包装的方法的名称或使用匿名方法实例化委托后,委托将把对它进行的方法调用传递给方法。调用方传递给委托的参数被传递给方法,来自方法的返回值(如果有)由委托返回给调用方。这被称为调用委托。可以将一个实例化的委托视为被包装的方法本身来调用该委托。例如:

 
// Create a method for a delegate.
public static void DelegateMethod(string message)
{
    System.Console.WriteLine(message);
}


 
// Instantiate the delegate.
Del handler = DelegateMethod;
// Call the delegate.
handler("Hello World");
委托类型派生自 .NET Framework 中的Delegate 类。委托类型是密封的,不能从Delegate 中派生委托类型,也不可能从中派生自定义类。由于实例化委托是一个对象,所以可以将其作为参数进行传递,也可以将其赋值给属性。这样,方法便可以将一个委托作为参数来接受,并且以后可以调用该委托。这称为异步回调,是在较长的进程完成后用来通知调用方的常用方法。以这种方式使用委托时,使用委托的代码无需了解有关所用方法的实现方面的任何信息。此功能类似于接口所提供的封装。有关更多信息,请参见何时使用委托而不使用接口

回调的另一个常见用法是定义自定义的比较方法并将该委托传递给排序方法。它允许调用方的代码成为排序算法的一部分。下面的示例方法使用Del
类型作为参数:

public void MethodWithCallback(int param1, int param2, Del callback)
{
    callback("The number is: " + (param1 + param2).ToString());
}


然后可以将上面创建的委托传递给该方法:

MethodWithCallback(1, 2, handler);

在控制台中将收到下面的输出:

The number is: 3

在将委托用作抽象概念时,MethodWithCallback 不需要直接调用控制台 -- 设计它时无需考虑控制台。MethodWithCallback
的作用只是准备字符串并将该字符串传递给其他方法。
此功能特别强大,因为委托的方法可以使用任意数量的参数。

将委托构造为包装实例方法时,该委托将同时引用实例和方法。除了它所包装的方法外,委托不了解实例类型,所以只要任意类型的对象中具有与委托签名相匹配的方法,委托就可以引用该对象。将委托构造为包装静态方法时,它只引用方法。考虑下列声明:

public class MethodClass
{
    public void Method1(string message) { }
    public void Method2(string message) { }
}


加上前面显示的静态 DelegateMethod,现在我们有三个方法可由 Del 实例进行包装。

调用委托时,它可以调用多个方法。这称为多路广播。若要向委托的方法列表(调用列表)中添加额外的方法,只需使用加法运算符或加法赋值运算符(“+”或“+=”)添加两个委托。例如:

MethodClass obj = new MethodClass();
Del d1 = obj.Method1;
Del d2 = obj.Method2;
Del d3 = DelegateMethod;
 
//Both types of assignment are valid.
Del allMethodsDelegate = d1 + d2;
allMethodsDelegate += d3;


此时,allMethodsDelegate 在其调用列表中包含三个方法 --Method1Method2DelegateMethod原来的三个委托d1d2
d3 保持不变。
调用allMethodsDelegate 时,将按顺序调用所有这三个方法。如果委托使用引用参数,则引用将依次传递给三个方法中的每个方法,由一个方法引起的更改对下一个方法是可见的。如果任一方法引发了异常,而在该方法内未捕获该异常,则该异常将传递给委托的调用方,并且不再对调用列表中后面的方法进行调用。如果委托具有返回值和/或输出参数,它将返回最后调用的方法的返回值和参数。若要从调用列表中移除方法,请使用减法运算符或减法赋值运算符(“-”或“-=”)。例如:

//remove Method1
allMethodsDelegate -= d1;
 
// copy AllMethodsDelegate while removing d2
Del oneMethodDelegate = allMethodsDelegate - d2;


由于委托类型派生自
System.Delegate
,所以可在委托上调用该类定义的方法和属性。

例如,为了找出委托的调用列表中的方法数,您可以编写下面的代码:

int invocationCount = d1.GetInvocationList().GetLength(0);


在调用列表中具有多个方法的委托派生自MulticastDelegate,这是System.Delegate
的子类。
由于两个类都支持GetInvocationList,所以上面的代码在两种情况下都适用。

多路广播委托广泛用于事件处理中。事件源对象向已注册接收该事件的接收方对象发送事件通知。为了为事件注册,接收方创建了旨在处理事件的方法,然后为该方法创建委托并将该委托传递给事件源。事件发生时,源将调用委托。然后,委托调用接收方的事件处理方法并传送事件数据。给定事件的委托类型由事件源定义。有关更多信息,请参见事件(C#
编程指南)

在编译时,对分配了两种不同类型的委托进行比较将产生编译错误。如果委托实例静态地属于类型System.Delegate,则允许进行比较,但在运行时将返回
false。
例如:

delegate void Delegate1();
delegate void Delegate2();
 
static void method(Delegate1 d, Delegate2 e, System.Delegate f)
{
    // Compile-time error.
    //Console.WriteLine(d == e);
 
    // OK at compile-time. False if the run-time type of f 
    // is not the same as that of d.
    System.Console.WriteLine(d == f);
}

何时使用委托而不使用接口

委托和接口都允许类设计器分离类型声明和实现。 任何类或结构都能继承和实现给定的接口。 可以为任何类上的方法创建委托,前提是该方法符合委托的方法签名。 接口引用或委托可由不了解实现该接口或委托方法的类的对象使用。 既然存在这些相似性,那么类设计器何时应使用委托,何时又该使用接口呢?

在以下情况下,请使用委托:

  • 当使用事件设计模式时。

  • 当封装静态方法可取时。

  • 当调用方不需要访问实现该方法的对象中的其他属性、方法或接口时。

  • 需要方便的组合。

  • 当类可能需要该方法的多个实现时。

在以下情况下,请使用接口:

  • 当存在一组可能被调用的相关方法时。

  • 当类只需要方法的单个实现时。

  • 当使用接口的类想要将该接口强制转换为其他接口或类类型时。

  • 当正在实现的方法链接到类的类型或标识时:例如比较方法。

IComparable 或泛型版本 IComparable(Of T) 就是一个使用单一方法接口而不使用委托的很好的示例。 IComparable 声明 CompareTo 方法,该方法返回一个整数,指定相同类型的两个对象之间的小于、等于或大于关系。 IComparable 可用作排序算法的基础。 虽然将委托比较方法用作排序算法的基础是有效的,但是并不理想。 因为进行比较的能力属于类,而比较算法不会在运行时改变,所以单一方法接口是理想的。

 

委托中的变体

.NET Framework 3.5 和 Visual Studio 2008 引入了变体支持,用于在 C# 和 Visual Basic 的所有委托中匹配方法签名和委托类型。 这意味着,您不仅可以为委托指派具有匹配签名的方法,而且可以指派这样的方法:它们返回与委托类型指定的派生类型相比,派生程度更大的类型(协变),或者接受相比之下,派生程度更小的类型的参数(逆变)。 这包括泛型委托和非泛型委托。

例如,请考虑下面的代码,其中涉及两个类和两个委托,泛型和非泛型各一个。

public class First { }
public class Second : First { }
public delegate First SampleDelegate(Second a);
public delegate R SampleGenericDelegate<A, R>(A a);


创建 SampleDelegateSampleGenericDelegate<A, R>(在 Visual Basic 中为 SampleDelegate(Of A, R))类型的委托时,可以为这些委托指派以下任一方法。

// Matching signature.
public static First ASecondRFirst(Second first)
{ return new First(); } 
// The return type is more derived.
public static Second ASecondRSecond(Second second)
{ return new Second(); }
// The argument type is less derived.
public static First AFirstRFirst(First first)
{ return new First(); }
// The return type is more derived 
// and the argument type is less derived.
public static Second AFirstRSecond(First first)
{ return new Second(); }

下面的代码示例展示了方法签名和委托类型之间的隐式转换。

// Assigning a method with a matching signature
// to a non-generic delegate. No conversion is necessary.
SampleDelegate dNonGeneric = ASecondRFirst;
// Assigning a method with a more derived return type
// and less derived argument type to a non-generic delegate.
// The implicit conversion is used.
SampleDelegate dNonGenericConversion = AFirstRSecond;
// Assigning a method with a matching signature to a generic delegate.
// No conversion is necessary.
SampleGenericDelegate<Second, First> dGeneric = ASecondRFirst;
// Assigning a method with a more derived return type
// and less derived argument type to a generic delegate.
// The implicit conversion is used.
SampleGenericDelegate<Second, First> dGenericConversion = AFirstRSecond;

有关更多示例,请参见在委托中使用变体(C# 和 Visual Basic)对 Func 和 Action 泛型委托使用变体(C# 和 Visual Basic)

泛型类型参数中的变体

在 .NET Framework 4 中,您可以在委托之间进行隐式转换,这样,如果泛型类型参数指定的不同类型按照变体的要求彼此相互继承,则可以将具有这些类型的泛型委托指派给彼此。

若要启用隐式转换,必须使用 inout 关键字,将委托中的泛型参数显式声明为协变或逆变。

下面的代码示例演示如何创建具有协变泛型类型参数的委托。

// Type T is declared covariant by using the out keyword.
public delegate T SampleGenericDelegate <out T>();

public static void Test()
{
    SampleGenericDelegate <String> dString = () => " ";

    // You can assign delegates to each other,
    // because the type T is declared covariant.
    SampleGenericDelegate <Object> dObject = dString;           
}


如果您仅使用变体支持来匹配方法签名和委托类型,且不使用 inout 关键字,则可能会发现,有时可以用相同的 lambda 表达式或方法来实例化多个委托,但无法将一个委托指派给另一个委托。

在下面的代码示例中,虽然 String 继承 Object,但无法将 SampleGenericDelegate<String> 显式转换为 SampleGenericDelegate<Object>(在 Visual Basic 中为将 SampleGenericDelegate(Of String) 转换为 SampleGenericDelegate(Of Object))。 您可以使用 out 关键字来标记泛型参数 T,从而解决此问题。

public delegate T SampleGenericDelegate<T>();public static void Test(){ SampleGenericDelegate<String> dString = () => " ";
// You can assign the dObject delegate
// to the same lambda expression as dString delegate
// because of the variance support for
// matching method signatures with delegate types.
SampleGenericDelegate<Object> dObject = () => " ";
// The following statement generates a compiler error
// because the generic type T is not marked as covariant.
// SampleGenericDelegate <Object> dObject = dString;}

.NET Framework 中具有变体类型参数的泛型委托

.NET Framework 4 中为若干现有泛型委托中的泛型类型参数引入了变体支持:

有关更多信息和示例,请参见对 Func 和 Action 泛型委托使用变体(C# 和 Visual Basic)

在泛型委托中声明变体类型参数

如果泛型委托具有协变或逆变泛型类型参数,则可以将该委托称为“变体泛型委托”。

您可以使用 out 关键字,在泛型委托中将泛型类型参数声明为协变。 协变类型只能用作方法返回类型,不能用作方法参数的类型。 下面的代码示例演示如何声明协变泛型委托。

public delegate R DCovariant<out R>();


您可以使用 in 关键字,在泛型委托中将泛型类型参数声明为逆变。 逆变类型只能用作方法参数的类型,不能用作方法返回类型。 下面的代码示例演示如何声明逆变泛型委托。

public delegate void DContravariant<in A>(A a);


重要说明重要事项

不能将
Visual Basic 中的 ByRef 参数和 C# 中的 refout 参数标记为变体。

此外还可以在同一接口中同时支持协变和逆变,但需应用于不同的类型参数。 下例演示了这种情况。

public delegate R DVariant<in A, out R>(A a);


实例化和调用变体泛型委托

您可以实例化和调用变体委托,就像实例化和调用固定委托一样。 在下例中,委托由 lambda 表达式进行实例化。

DVariant<String, String> dvariant = (String str) => str + " ";
dvariant("test");


组合变体泛型委托

不应该组合变体委托。 Combine 方法不支持变体委托转换,并且希望委托的类型完全相同。 这可能会在使用 Combine 方法(在 C# 和 Visual Basic 中)或使用 + 运算符(在 C# 中)组合委托时导致运行时异常,如下面的代码示例所示。

Action<object> actObj = x => Console.WriteLine("object: {0}", x);
Action<string> actStr = x => Console.WriteLine("string: {0}", x);
// All of the following statements throw exceptions at run time.
// Action<string> actCombine = actStr + actObj;
// actStr += actObj;
// Delegate.Combine(actStr, actObj);


只有引用类型才支持泛型类型参数的变体。 例如,无法将 DVariant<int>(在 Visual Basic 中为 DVariant(Of Int))隐式转换为 DVariant<Object>DVaraint<long>(在 Visual Basic 中为 DVariant(Of Object)DVaraint(Of Long)),因为整数为值类型。

下例说明了值类型不支持泛型类型参数中的变体。

// The type T is covariant.
public delegate T DVariant<out T>();
// The type T is invariant.
public delegate T DInvariant<T>();
public static void Test()
{
    int i = 0;
    DInvariant<int> dInt = () => i;
    DVariant<int> dVariantInt = () => i;
 
    // All of the following statements generate a compiler error
    // because type variance in generic parameters is not supported
    // for value types, even if generic type parameters are declared variant.
    // DInvariant<Object> dObject = dInt;
    // DInvariant<long> dLong = dInt;
    // DVariant<Object> dVariantObject = dVariantInt;
    // DVariant<long> dVariantLong = dVariantInt;            
}

当向委托分配一个方法时,协变和逆变会提供用于使委托类型与方法签名匹配的灵活性。 协变允许方法具有的派生返回类型比委托中定义的更多。 逆变允许方法具有的派生参数类型比委托类型中的更少。

示例 1:协变

说明

本示例演示如何将委托与具有返回类型的方法一起使用,这些返回类型派生自委托签名中的返回类型。 DogsHandler 返回的数据类型是 Dogs 类型,它是由委托中定义的 Mammals 类型派生的。

代码

class Mammals{}
class Dogs : Mammals{}
 
class Program
{
    // Define the delegate.
    public delegate Mammals HandlerMethod();
    public static Mammals MammalsHandler()
    {
        return null;
    }
 
    public static Dogs DogsHandler()
    {
        return null;
    }
 
    static void Test()
    {
        HandlerMethod handlerMammals = MammalsHandler;
 
        // Covariance enables this assignment.
        HandlerMethod handlerDogs = DogsHandler;
    }
}


示例 2:逆变


说明

本示例演示如何将委托与具有某个类型的参数的方法一起使用,这些参数是委托签名参数类型的基类型。 使用逆变,可以使用一个事件处理程序而不是多个单独的处理程序。 例如,可以创建接受 EventArgs 输入参数的事件处理程序,并将其与将 MouseEventArgs 类型作为参数发送的 Button.MouseClick 事件一起使用,也可以将其与发送 KeyEventArgs 参数的 TextBox.KeyDown 事件一起使用。

代码

// Event hander that accepts a parameter of the EventArgs type.
private void MultiHandler(object sender, System.EventArgs e)
{
    label1.Text = System.DateTime.Now.ToString();
}

public Form1()
{
    InitializeComponent();
 
    // You can use a method that has an EventArgs parameter,
    // although the event expects the KeyEventArgs parameter.
    this.button1.KeyDown += this.MultiHandler;

    // You can use the same method 
    // for an event that expects the MouseEventArgs parameter.
    this.button1.MouseClick += this.MultiHandler;
}

Lambda 表达式

Visual Studio 2010

“Lambda 表达式”是一个匿名函数,它可以包含表达式和语句,并且可用于创建委托或表达式树类型。

所有 Lambda 表达式都使用 Lambda 运算符 =>,该运算符读为“goes to”。 该 Lambda 运算符的左边是输入参数(如果有),右边包含表达式或语句块。 Lambda 表达式 x => x * x 读作“x goes to x times x”。可以将此表达式分配给委托类型,如下所示:

delegate int del(int i);
static void Main(string[] args)
{
    del myDelegate = x => x * x;
    int j = myDelegate(5); //j = 25
}

创建表达式树类型:

using System.Linq.Expressions;
namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Expression<del> myET = x => x * x;
        }
    }
}
 

=> 运算符具有与赋值运算符 (=) 相同的优先级,并且是右结合运算符。

Lambda 在基于方法的 LINQ 查询中用作标准查询运算符方法(如 Where)的参数。

使用基于方法的语法在 Enumerable 类中调用 Where 方法时(像在 LINQ to Objects 和 LINQ to XML 中那样),参数是委托类型 System.Func<T, TResult> 使用 Lambda 表达式创建委托最为方便。 例如,当您在 System.Linq.Queryable 类中调用相同的方法时(像在 LINQ to SQL 中那样),则参数类型是 System.Linq.Expressions.Expression<Func>,其中 Func 是包含至多十六个输入参数的任何 Func 委托。 同样,Lambda 表达式只是一种用于构造表达式树的非常简练的方式。 尽管事实上通过 Lambda 创建的对象的类型是不同的,但 Lambda 使得 Where 调用看起来类似。

在前面的示例中,请注意委托签名具有一个 int 类型的隐式类型输入参数,并返回 int 可以将 Lambda 表达式转换为该类型的委托,因为该表达式也具有一个输入参数 (x),以及一个编译器可隐式转换为 int 类型的返回值。 (以下几节中将对类型推理进行详细讨论。)使用输入参数 5 调用委托时,它将返回结果 25。

isas 运算符的左侧不允许使用 Lambda。

适用于匿名方法的所有限制也适用于 Lambda 表达式。 有关更多信息,请参见 匿名方法(C# 编程指南)


表达式在右边的 Lambda 表达式称为“Lambda 表达式”。 Lambda 表达式在构造表达式树(C# 和 Visual Basic)时广泛使用。 Lambda 表达式返回表达式的结果,并采用以下基本形式:

(input parameters) => expression

只有在 Lambda 有一个输入参数时,括号才是可选的;否则括号是必需的。 两个或更多输入参数由括在括号中的逗号分隔:

(x, y) => x == y

有时,编译器难于或无法推断输入类型。 如果出现这种情况,您可以按以下示例中所示方式显式指定类型:

(int x, string s) => s.Length > x
使用空括号指定零个输入参数:
() => SomeMethod()
在上一个示例中,请注意 Lambda 表达式的主体可以包含方法调用。 但是,如果要创建将在另一个域(比如 SQL Server)中使用的表达式树,则不应在 Lambda 表达式中使用方法调用。 方法在 .NET 公共语言运行时上下文的外部将没有意义。 

Lambda 语句与 Lambda 表达式类似,只是语句括在大括号中:

(input parameters) => {statement;}

Lambda 语句的主体可以包含任意数量的语句;但是,实际上通常不会多于两个或三个语句。

delegate void TestDelegate(string s);
TestDelegate myDel = n => { string s = n + " " + "World"; Console.WriteLine(s); };
myDel("Hello");

像匿名方法一样,Lambda 语句无法用于创建表达式树。

许多标准查询运算符都具有输入参数,其类型是泛型委托的 Func<T, TResult> 系列的其中之一。 Func<T, TResult> 委托使用类型参数定义输入参数的数目和类型,以及委托的返回类型。 Func 委托对于封装应用于一组源数据中每个元素的用户定义表达式非常有用。 例如,假设有以下委托类型:

public delegate TResult Func<TArg0, TResult>(TArg0 arg0)
可以将委托实例化为 Func<int,bool> myFunc,其中 int 是输入参数,bool 是返回值。 始终在最后一个类型参数中指定返回值。 Func<int, string, bool> 定义包含两个输入参数(intstring)且返回类型为 bool 的委托。 在调用下面的 Func 委托时,该委托将返回 true 或 false 以指示输入参数是否等于 5: 
Func<int, bool> myFunc = x => x == 5;
bool result = myFunc(4); // returns false of course

当参数类型为 Expression<Func> 时,您也可以提供 Lambda 表达式,例如在 System.Linq.Queryable 内定义的标准查询运算符中。 如果指定 Expression<Func> 参数,Lambda 将编译为表达式树。

此处显示了一个标准查询运算符,Count 方法:

int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
int oddNumbers = numbers.Count(n => n % 2 == 1);
编译器可以推断输入参数的类型,或者您也可以显式指定该类型。 这个特别的 Lambda 表达式将计算整数 (n) 的数量,这些整数除以 2 时余数为 1。 

以下方法将生成一个序列,其中包含 numbers 数组中在 9 左边的所有元素,因为 9 是序列中不满足条件的第一个数字:

var firstNumbersLessThan6 = numbers.TakeWhile(n => n < 6);
此示例演示如何通过将输入参数括在括号中来指定多个输入参数。 该方法将返回数字数组中的所有元素,直至遇到一个值小于其位置的数字为止。 不要将 Lambda 运算符 (=>) 与大于等于运算符 (>=) 混淆。 
var firstSmallNumbers = numbers.TakeWhile((n, index) => n >= index);

在编写 Lambda 时,通常不必为输入参数指定类型,因为编译器可以根据 Lambda 主体、基础委托类型以及 C# 语言规范中描述的其他因素推断类型。 对于大多数标准查询运算符,第一个输入是源序列中的元素的类型。 因此,如果要查询 IEnumerable<Customer>,则输入变量将被推断为 Customer 对象,这意味着您可以访问其方法和属性:

customers.Where(c => c.City == "London");

Lambda 的一般规则如下:

  • Lambda 包含的参数数量必须与委托类型包含的参数数量相同。

  • Lambda 中的每个输入参数必须都能够隐式转换为其对应的委托参数。

  • Lambda 的返回值(如果有)必须能够隐式转换为委托的返回类型。

请注意,Lambda 表达式本身没有类型,因为常规类型系统没有“Lambda 表达式”这一内部概念。但是,有时会不正式地论及 Lambda 表达式的“类型”。 在这些情况下,类型是指委托类型或 Lambda 表达式所转换为的 Expression 类型。

Lambda 可以引用“外部变量”,这些变量位于在其中定义 Lambda 的封闭方法或类型的范围内。 将会存储通过这种方法捕获的变量以供在 Lambda 表达式中使用,即使变量将以其他方式超出范围或被作为垃圾回收。 必须明确地分配外部变量,然后才能在 Lambda 表达式中使用该变量。 下面的示例演示这些规则:

delegate bool D();
delegate bool D2(int i);
 
class Test
{
    D del;
    D2 del2;
    public void TestMethod(int input)
    {
        int j = 0;
        // Initialize the delegates with lambda expressions.
        // Note access to 2 outer variables.
        // del will be invoked within this method.
        del = () => { j = 10;  return j > input; };
        // del2 will be invoked after TestMethod goes out of scope.
        del2 = (x) => {return x == j; };
      
        // Demonstrate value of j:
        // Output: j = 0 
        // The delegate has not been invoked yet.
        Console.WriteLine("j = {0}", j);        // Invoke the delegate.
        bool boolResult = del();
        // Output: j = 10 b = True
        Console.WriteLine("j = {0}. b = {1}", j, boolResult);
    }
 
    static void Main()
    {
        Test test = new Test();
        test.TestMethod(5);
        // Prove that del2 still has a copy of
        // local variable j from TestMethod.
        bool result = test.del2(10);
 
        // Output: True
        Console.WriteLine(result);
           
        Console.ReadKey();
    }
}

下列规则适用于 Lambda 表达式中的变量范围:

  • 捕获的变量将不会被作为垃圾回收,直至引用变量的委托超出范围为止。

  • 在外部方法中看不到 Lambda 表达式内引入的变量。

  • Lambda 表达式无法从封闭方法中直接捕获 refout 参数。

  • Lambda 表达式中的返回语句不会导致封闭方法返回。

  • Lambda 表达式不能包含其目标位于所包含匿名函数主体外部或内部的 goto 语句、break 语句或 continue 语句。

 

  

对 Func 和 Action 泛型委托使用变体

下例演示如何在 FuncAction 泛型委托中使用协变和逆变,以便能够在代码中重用方法及提供更大的灵活性。

有关协变和逆变的更多信息,请参见委托中的变体(C# 和 Visual Basic)


下例展示了 Func 泛型委托中的协变支持带来的益处。 FindByTitle 方法获取 String 类型的参数,返回 Employee 类型的对象。 但是,您可以将此方法指派给 Func<String, Person> 委托(在 Visual Basic 中为 Func(Of String, Person)),因为 Employee 继承 Person

// Simple hierarchy of classes.
public class Person { }
public class Employee : Person { }
class Program
{
    static Employee FindByTitle(String title)
    {
        // This is a stub for a method that returns
        // an employee that has the specified title.
        return new Employee();
    }
    static void Test()
    {
        // Create an instance of the delegate without using variance.
        Func<String, Employee> findEmployee = FindByTitle;
 
        // The delegate expects a method to return Person,
        // but you can assign it a method that returns Employee.
        Func<String, Person> findPerson = FindByTitle;
        // You can also assign a delegate 
        // that returns a more derived type 
        // to a delegate that returns a less derived type.
        findPerson = findEmployee;
 
    }
}


使用具有逆变类型参数的委托

下例展示了 Action 泛型委托中的逆变支持带来的益处。 AddToContacts 方法获取 Person 类型的参数。 但是,您可以将此方法指派给 Action<Employee> 委托(在 Visual Basic 中为 (Action(Of Employee)),因为 Employee 继承 Person

public class Person { }
public class Employee : Person { }
class Program
{
    static void AddToContacts(Person person)
    {
        // This method adds a Person object
        // to a contact list.
    }
    static void Test()
    {
        // Create an instance of the delegate without using variance.
        Action<Person> addPersonToContacts = AddToContacts;
        // The Action delegate expects 
        // a method that has an Employee parameter,
        // but you can assign it a method that has a Person parameter
        // because Employee derives from Person.
        Action<Employee> addEmployeeToContacts = AddToContacts;
        // You can also assign a delegate 
        // that accepts a less derived parameter to a delegate 
        // that accepts a more derived parameter.
        addEmployeeToContacts = addPersonToContacts;
    }
}


抱歉!评论已关闭.