委托,为什么要使用?

58

可能的重复问题:
在 C# 中什么时候使用委托?
委托的作用是什么?

我看到很多有关使用委托的问题。我仍然不清楚在哪里和为什么会使用委托而不是直接调用方法。

我听过这句话很多次:“然后可以将委托对象传递给代码,该代码可以调用被引用的方法,而无需在编译时知道将调用哪个方法。”

我不明白这个说法如何正确。

我编写了以下示例。假设您有3个具有相同参数的方法:

   public int add(int x, int y)
    {
        int total;
        return total = x + y;
    }
    public int multiply(int x, int y)
    {
        int total;
        return total = x * y;
    }
    public int subtract(int x, int y)
    {
        int total;
        return total = x - y;
    }

现在我声明一个委托:

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

现在我可以更进一步,声明一个处理程序来使用这个委托(或者直接使用你的委托)

调用委托:

MyClass f = new MyClass();

Operations p = new Operations(f.multiply);
p.Invoke(5, 5);

或者使用处理程序进行调用

f.OperationsHandler = f.multiply;
//just displaying result to text as an example
textBoxDelegate.Text = f.OperationsHandler.Invoke(5, 5).ToString();
在这两种情况下,我看到我的“multiply”方法被指定。为什么人们使用“在运行时更改功能”这个短语或上面那个短语?
如果每次声明委托时都需要一个方法来指向,为什么要使用委托?如果需要指向一个方法,为什么不直接调用该方法?在我看来,使用委托需要编写比直接使用函数更多的代码。
可以有人给我举个现实世界中的例子吗?我完全感到困惑。

6
+1 - 好问题,法师!我期待一些回答。 - Buggabill
20
你应该真正地审查一下此主题的先前问题。我在几分钟内找到了四个可能的重复问题(其中一些有非常好的答案):委托的目的何时使用C#的委托?使用委托的优势是什么?委托不只是简写接口吗? - Jeff Sternal
1
尽管这个问题很受欢迎,但它应该被关闭并与其他至少四个重复的问题合并。 - user195488
@Jeff Sternal,您看到Joel Coehoorn的回答在Meta上得到了-1。这是有原因的。 - George Stocker
虽然有重复,但更简单并且解决了我和几位同事在阅读其他四个“原始”答案时遇到的许多问题。 - Mkl Rjv
显示剩余7条评论
13个回答

29

在运行时更改功能不是委托所能完成的。

基本上,委托可以省去大量的输入。

例如:

class Person
{
    public string Name { get; }
    public int Age { get; }
    public double Height { get; }
    public double Weight { get; }
}

IEnumerable<Person> people = GetPeople();

var orderedByName = people.OrderBy(p => p.Name);
var orderedByAge = people.OrderBy(p => p.Age);
var orderedByHeight = people.OrderBy(p => p.Height);
var orderedByWeight = people.OrderBy(p => p.Weight);

在以上代码中,p => p.Namep => p.Age等都是lambda表达式,它们被求值为Func<Person, T>委托(其中T分别为stringintdoubledouble)。
现在让我们考虑如何在不使用委托的情况下实现上述操作。我们必须放弃通用性,并定义这些方法:
public static IEnumerable<Person> OrderByName(this IEnumerable<Person> people);
public static IEnumerable<Person> OrderByAge(this IEnumerable<Person> people);
public static IEnumerable<Person> OrderByHeight(this IEnumerable<Person> people);
public static IEnumerable<Person> OrderByWeight(this IEnumerable<Person> people);

这将会非常糟糕。首先,代码的可重用性已经无限降低,因为它仅适用于“Person”类型的集合。此外,我们需要将完全相同的代码复制粘贴四次,每次仅更改1或2行(引用“Person”的相关属性——否则看起来都一样)!这很快会变成一个难以维护的混乱局面。
所以,委托使您的代码更具可重用性和可维护性,通过在代码中抽象出可以切换的某些行为来实现。

1
将一个函数作为参数传递给另一个函数,可以在运行时选择要传递的函数,这就是“在运行时更改功能”。 - alpav
@alpav:好的。我理解这句话的意思是将任意功能分配给不打算进行修改的现有类,也就是所谓的Monkeypatching(例如,通过重新打开类或者通过将函数分配给任意属性来实现Ruby或JavaScript)。我的观点是你不能动态地改变任何老旧类的实现方式。但是如果一个类被设计为基于委托具有可定制功能,则确实可以在运行时更改它。 - Dan Tao

20

非常棒的文章,感谢分享链接! - Thomas Levesque

12
代理非常有用,特别是在 linq 和闭包引入后。
一个很好的例子是“Where”函数,它是标准 linq 方法之一。“Where”接受一个列表和一个过滤器,并返回与过滤器匹配的项的列表。(过滤器参数是一个接受 T 并返回布尔值的委托。)
由于它使用委托来指定过滤器,因此 Where 函数非常灵活。例如,你不需要不同的 Where 函数来过滤奇数和质数。调用语法也非常简洁,如果你使用接口或抽象类则不会是这种情况。
更具体地说,Where 使用委托意味着你可以编写以下代码:
var result = list.Where(x => x != null);
...

改为这样:

var result = new List<T>();
foreach (var e in list)
    if (e != null)
        result.add(e)
...

9
为什么使用委托,每次我声明一个委托都需要指向一个方法?如果它需要指向一个方法,为什么不直接调用该方法?与接口类似,委托允许您解耦并泛化您的代码。通常在您事先不知道要执行哪些方法时使用委托-当您只知道要执行与特定签名匹配的某些内容时使用委托。例如,考虑一个计时器类,定期执行某些方法:
public delegate void SimpleAction();

public class Timer {
    public Timer(int secondsBetweenActions, SimpleAction simpleAction) {}
}

你可以将任何东西插入到这个计时器中,因此你可以在其他任何项目或应用中使用它,而不需要预测你将如何使用它,也不会限制其仅可用于少数几种场景,而这些场景仅在你此刻正在思考。


很好的例子,但为什么不直接在类中添加一个执行相同操作的方法呢?委托只是为了增加代码的优雅性吗?我明白,但又不太明白。 - user3308043

7

让我给您举个例子。如果您的类公开了一个事件,那么在运行时它可以被分配一些委托,以便在发生某些事情时调用这些委托。当您编写该类时,您不知道哪些委托将最终得到运行。相反,这是由使用您的类的人确定的。


3

需要使用委托的一个例子是,当您必须在UI线程中修改控件并且您正在另一个线程中操作时。例如,

public delegate void UpdateTextBox(string data);

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    ...
    Invoke(new UpdateTextBox(textBoxData), data);
    ...

}

private void textBoxData(string data)
{
            textBox1.Text += data;
}

3

在你的示例中,一旦将委托分配给变量,就可以像任何其他变量一样传递它。您可以创建一个接受委托作为参数的方法,并且它可以调用委托而不需要知道该方法实际上声明在哪里。

private int DoSomeOperation( Operations operation )
{
    return operation.Invoke(5,5);
}

...

MyClass f = new MyClass();
Operations p = new Operations(f.multiply);
int result = DoSomeOperation( p );

委托将方法转换为可以像int一样传递的东西。你可以说变量并没有给你任何额外的东西,因为在

HTML中,您可以将方法作为参数传递给其他方法或赋值给变量。

这使得代码更加灵活和模块化,因为您可以将一个方法定义放在一个地方,然后在整个代码库中使用该方法而不必复制和粘贴该方法的多个实例。

int i = 5;
Console.Write( i + 10 );

你看到数值5被指定,所以你也可以直接写Console.Write( 5 + 10 )。在这种情况下是正确的,但它忽略了能够说出以下内容的好处:

DateTime nextWeek = DateTime.Now.AddDays(7);

不必定义特定的DateTime.AddSevenDays()方法和AddSixDays方法等,而是可以使用以下方式:


2
举个具体的例子,我最近使用委托的一个例子是在System.Net.Mail.SmtpClient上的SendAsync()方法。 我有一个发送大量邮件的应用程序,并且等待Exchange服务器接受消息时会出现明显的性能问题。 但是,需要记录与该服务器的交互结果。
所以,我编写了一个委托方法来处理该记录,并在发送每封电子邮件时将其传递给SendAsync()(我们之前只使用Send())。 这样它就可以回调委托以记录结果,而应用程序线程不会在交互完成之前等待。
对于任何希望应用程序在等待交互完成的情况下继续运行的外部IO都是如此。 Web服务的代理类等也利用这一点。

1

以您的Operations示例为例,想象一下有几个按钮的计算器。 您可以像这样创建一个按钮类

class CalcButton extends Button {
   Operations myOp;
   public CalcButton(Operations op) {
      this.myOp=op; 
   }
   public void OnClick(Event e) {
      setA( this.myOp(getA(), getB()) ); // perform the operation
   }
}

然后当您创建按钮时,可以为每个按钮创建不同的操作

CalcButton addButton = new CalcButton(new Operations(f.multiply));

这样做有几个好处。您不需要在按钮中复制代码,它们是通用的。您可以拥有多个按钮,所有这些按钮都具有相同的操作,例如在不同的面板或菜单上。您可以随时更改与按钮关联的操作。


1

看起来许多答案都与内联委托有关,在我看来,内联委托比我所谓的“经典委托”更容易理解。

下面是我的例子,展示了委托如何允许消费类更改或增强行为(通过添加“钩子”使消费者能够在关键操作之前或之后执行某些操作和/或完全防止该行为)。请注意,所有决策逻辑都是从StringSaver类外部提供的。现在考虑可能会有4个不同的使用此类的消费者——它们每个人都可以实现自己的VerificationNotification逻辑,或者根据需要不实现任何逻辑。

internal class StringSaver
{
    public void Save()
    {
        if(BeforeSave != null)
        {
            var shouldProceed = BeforeSave(thingsToSave);
            if(!shouldProceed) return;
        }
        BeforeSave(thingsToSave);

        // do the save

        if (AfterSave != null) AfterSave();
    }

    IList<string> thingsToSave;
    public void Add(string thing) { thingsToSave.Add(thing); }

    public Verification BeforeSave;
    public Notification AfterSave;
}

public delegate bool Verification(IEnumerable<string> thingsBeingSaved);
public delegate void Notification();

public class SomeUtility
{
    public void SaveSomeStrings(params string[] strings)
    {
        var saver = new StringSaver
            {
                BeforeSave = ValidateStrings, 
                AfterSave = ReportSuccess
            };

        foreach (var s in strings) saver.Add(s);

        saver.Save();
    }

    bool ValidateStrings(IEnumerable<string> strings)
    {
        return !strings.Any(s => s.Contains("RESTRICTED"));
    }

    void ReportSuccess()
    {
        Console.WriteLine("Saved successfully");
    }
}

我想重点是委托所指向的方法不一定在公开委托成员的类中。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接