C# 委托和抽象类

6

我目前在两个抽象类中有2种具体方法。一个类包含当前方法,而另一个类包含旧版方法。例如:

// Class #1
public abstract class ClassCurrent<T> : BaseClass<T> where T : BaseNode, new()
{
    public List<T> GetAllRootNodes(int i)
    {
      //some code
    }
}

// Class #2
public abstract class MyClassLegacy<T> : BaseClass<T> where T : BaseNode, new()
{
    public List<T> GetAllLeafNodes(int j)
    {
      //some code
    }
}

我希望你能在应用程序的相关场景中运行相应的方法。我计划编写一个委托来处理这个问题。我的想法是,我只需调用委托并在其中编写逻辑以处理根据从哪个类/项目调用它而调用哪个方法(至少我认为委托是用来做这个的,以及它们如何被使用)。
然而,在这个主题上我有一些问题(在一些谷歌搜索之后):
1)是否可能有一个委托知道驻留在不同类中的2个或更多个方法? 2)是否可能使一个委托产生抽象类(就像上面的代码)?(我的猜测是不可能的,因为委托创建传入类的具体实现) 3)我尝试为上面的代码编写一个委托。但我遇到了技术上的挑战。
    public delegate List<BaseNode> GetAllNodesDelegate(int k);
    GetAllNodesDelegate del = new GetAllNodesDelegate(ClassCurrent<BaseNode>.GetAllRootNodes);

我收到了以下错误信息:
An object reference is required for the non-static field, method, property ClassCurrent<BaseNode>.GetAllRootNodes(int)

我可能误解了一些东西...但是如果我必须在调用类中手动声明委托,并像上面那样手动传递函数,那么我开始怀疑委托是否是处理我的问题的好方法。

谢谢。

5个回答

12
您尝试使用委托的方式(使用new构造委托,声明具有名称的委托类型),这表明您正在使用C# 1。如果您实际上使用的是C#3,则比那容易得多。
首先,看一下您的委托类型:
public delegate List<BaseNode> GetAllNodesDelegate(int k);

已经存在,就是这样:

Func<int, List<BaseNode>>

所以您不需要声明自己的版本。

其次,您应该将代理视为类似于具有一个方法的接口,您可以在不必编写命名类的情况下即时“实现”它。只需编写lambda表达式或直接分配方法名称即可。

Func<int, List<BaseNode>> getNodesFromInt;

// just assign a compatible method directly
getNodesFromInt = DoSomethingWithArgAndReturnList;

// or bind extra arguments to an incompatible method:
getNodesFromInt = arg => MakeList(arg, "anotherArgument");

// or write the whole thing specially:
getNodesFromInt = arg =>
    {
        var result = new List<BaseNode>();
        result.Add(new BaseNode());
        return result;
    };
一个lambda表达式的形式是(参数) => { 函数体; }。参数使用逗号进行分隔。如果只有一个参数,可以省略括号。如果不需要参数,使用一对空括号:()。如果函数体只有一条语句,可以省略大括号。如果函数体只有一个表达式,可以同时省略大括号和return关键字。在函数体中,可以引用封闭作用域中的任何变量和方法(除了与封闭方法相关的ref/out参数)。
几乎没有必要使用new来创建委托实例。很少有需要声明自定义委托类型的情况。对于返回值的委托,使用Func;对于返回void 的委托,使用Action
每当需要传递类似于具有一个方法的对象(无论是接口还是类)时,请使用委托,您将能够避免许多混乱。
特别要避免定义只有一个方法的接口。这将意味着您将不得不为每个不同的实现声明一个单独的命名类,而不能像编写lambda表达式那样实现该方法。
class Impl : IOneMethod
{
    // a bunch of fields

    public Impl(a bunch of parameters)
    {
        // assign all the parameters to their fields
    }

    public void TheOneMethod()
    {
        // make use of the fields
    }
}

使用lambda表达式可以为您实现所有这些操作,从而消除代码中的机械模式。您只需这样说:

() => /* same code as in TheOneMethod */

闭包还有一个优点,就是你可以更新封闭作用域中的变量,因为你可以直接引用它们(而不是使用复制到类字段中的值进行操作)。有时候这可能是一个缺点,如果你不想修改这些值。


如果您希望委托映射到特定方法,例如ClassCurrent(获取所有根节点)类型和另一种方法,例如MyClassLegacy(获取所有子节点)类型,则需要知道实例的类型。而如果它们共享一个常见接口,则只需知道它实现了给定的接口即可,无需知道类型。设置每种类型的委托的代码将包含一个if来检查类型或为每个允许的类型使用特定类型作为参数的方法。 - Cornelius
2
委托比函数指针更强大,因为它们是“一等方法”并具有闭包,但这只允许您以多态方式传递它们,并不会消除设置的复杂性,就像上面的评论中所述。 - Cornelius
如果每个类都有同样的方法,并且该方法在共同的接口或基类中共享,那么不需要写类型依赖的名称,否则会导致代码量增加。而且这些代码与实现接口的大小相似。每次为每种类型设置委托时需要 if (type A) d = instance.B() else if (type B) d = instance.C(),这将涉及运行时类型检查,这不是一个很好的面向对象设计。 - Cornelius
您可能忘记提到使用接口可以实现依赖注入,从而将行为与字段(属性)解耦?因此,是的,需要编写更多的代码,但这为未来的修改或增强提供了更大的灵活性。 - DaniDev
@DaniDev 大多数依赖注入系统也支持使用委托类型来描述服务。 - Daniel Earwicker
显示剩余2条评论

1

关于您的问题:
1)我不确定您所谓的“知道”是什么意思。您可以将任何方法传递给委托,因此如果您编写了了解其他某些方法的方法,则可以执行类似的委托。
2)同样,委托可以从可以执行的任何方法创建。例如,如果您有一个类型为ClassCurrent<T>的已初始化局部变量,则可以为ClassCurrent<T>类型的任何实例方法创建委托。
3)委托只能调用实际可调用的方法。我的意思是,您无法调用ClassCurrent.GetAllRootNodes,因为GetAllRootNodes不是静态方法,因此需要实例ClassCurrent来调用它。

委托可以留在具有访问ClassCurrentMyClassLegacy的任何类中。

例如,您可以创建如下内容:

您可以使用代表,该代表由引用到不同方法的初始化而来,具体取决于某些条件。

class SomeActionAccessor<T>
{
    // Declare delegate and fied of delegate type.
    public delegate T GetAllNodesDelegate(int i);

    private GetAllNodesDelegate getAllNodesDlg;

    // Initilaize delegate field somehow, e.g. in constructor.
    public SomeActionAccessor(GetAllNodesDelegate getAllNodesDlg)
    {
        this.getAllNodesDlg = getAllNodesDlg;
    }

    // Implement the method that calls the delegate.
    public T GetAllNodes(int i)
    {
        return this.getAllNodesDlg(i);
    }
}

委托可以包装静态和实例方法。唯一的区别是,对于使用实例方法创建委托,您需要拥有该方法的类的实例。

一旦代表被创建,例如public delegate void del(int i),代码将留在特定类中。 我认为代表需要留在具有感兴趣方法的类中,否则它将无法找到它们。 另外,我不明白为什么代表可以创建静态方法? - BeraCim

1

ClassCurrentMyClassLegacy都实现接口INodeFetcher

public interface INodeFetcher<T> { 
    List<T> GetNodes(int k);
} 

对于 ClassCurrent,从接口的实现中调用 GetAllRootNodes 方法;对于 MyLegacyClass,使用 GetAllLeaveNodes 方法。


3
如果你的接口中只有一个方法,使用委托代替。这样可以节省大量的代码。委托就像是只有一个方法的接口,而且语言支持非常强大,这意味着每次想要实现它时不必编写单独的命名类。你在这里需要的类型是 Func<int, List<T>> - Daniel Earwicker
@Daniel 但是如果你不在一个公共基类或接口上声明委托,你将无法使用多态的委托,并且你仍然需要设置委托,这也需要相当数量的代码。 - Cornelius
看我的答案。设置委托需要的代码比声明和实现接口要少得多。而且委托确实是多态的:调用者只需获得一个值,通过它可以进行方法调用,他们不会被绑定到特定的实现。这就像一个只有一个方法的接口,但使用它所需的仪式代码要少得多。 - Daniel Earwicker

0
为什么要使用委托?这听起来过于复杂了。我会创建一个新类中的方法,当需要调用该方法时,可以实例化该类。该类可以提供一些上下文信息以帮助它做出决策。然后,在新方法中实现逻辑,以决定是调用当前方法还是旧方法。
代码示例:
public class CurrentOrLegacySelector<T>
{

  public CurrentOrLegacySelector(some type that describe context)
  {   
     // .. do something with the context. 
     // The context could be a boolean or something more fancy.
  }

  public List<T> GetNodes(int argument) 
  {
    // Return the result of either current or
    // legacy method based on context information
  }
}

这将为您提供一个干净的包装方法,易于阅读和理解。

我认为使用委托的解决方案更加灵活 - 您将需要较少的操作来支持第三种方法。另一方面,第三种方法可能在未来不会出现 :) - Andrew Bezzub
委托并不更复杂,它实际上更简单。 - Daniel Earwicker

0
作为Rune Grimstad建议的主题变体,我认为你可以使用策略模式(例如
C#中GOF策略模式的介绍
)。
在无法更改LegacyClass(因此可能无法轻松使用Cornelius建议的“接口方法”)并且正在使用依赖注入(DI;依赖注入)的情况下,这将特别有趣。 DI会(也许)让您在正确的位置注入正确的实现(具体策略)。
策略:
public interface INodeFetcher<T> { 
    List<T> GetNodes(int k);
}

具体策略:

public class CurrentSelector<T> : INodeFetcher<T>
{
    public List<T> GetNodes(int argument) 
    {
    // Return the result "current" method
    }
}

public class LegacySelector<T> : INodeFetcher<T>
{
    public List<T> GetNodes(int argument) 
    {
    // Return the result "legacy" method
    }
}

-> 注入/实例化正确的具体策略。

敬礼


但是该接口只有一个方法。这只是一种不必要的手动编码的方法,使用委托可以消除这种情况。 - Daniel Earwicker
我哪里漏掉了什么?顺便说一下,您可能已经注意到我不太擅长委托编程;如果您能请温柔点 :) - scherand
你看到了我的回答吗?我不确定还需要添加什么……如果你只关心你的需求今天看起来如何,那么你不需要以任何特定的方式组织你的代码。几乎所有高级编程语言特性都是为了简化程序未来的演进。所以今天你可能只有两个实现,但将来呢? - Daniel Earwicker
@Daniel Earwicker - 我看到了你的回答,但我恐怕并没有完全理解,抱歉。 我认为我的困惑来自于这一行 getNodesFromInt = DoSomethingWithArgAndReturnList;。要使用它,DoSomething... 必须在某个地方被定义,对吗?如果是这样,我就看不出这个“某个地方”和具体的策略之间有什么区别。两者都会(在)一个单独的类中,不是吗? 如果有像 ArrayList.Sort(...) 中可能有无数种方法可以(在将来)做某事,那么情况就不同了。但如果只有5或10种方法可以做某事,并且我需要做 - scherand
我的代码中到处都是“something”,我不需要每次需要它时都要“实现”(例如使用lambda)委托吧(即使我在代码的其他地方再次执行相同的操作)?如果我不想这样做,当然可以将其放在单独的类中,并使用getNodesFromInt = DoSomethingWithArgAndReturnList;-style语法来使用它。但是那样我又回到了起点...希望我的解释清楚易懂。 - scherand

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