使用接口还是Func或Action函数?

9
我已经写了10年的C#代码,但是我不太清楚何时应该使用接口而不是使用Func或Action。在很多调用接口方法的地方,似乎使用Func或Action同样有效。所以我的问题是:如果我有一个只有一个方法或几个方法的接口,那么使用Func或Action会有什么劣势吗?对我来说,使用Func或Action更加简洁。
非常感谢。

2
也许你可以通过一个简单的例子更清楚地表达你的问题,从而询问应该使用其中的哪一个。 - deepee1
我认为我已经涵盖了何时最好使用它们。它们就像不同类的容器,所有类都共享一些公共功能(具有不同的实现)。 - Shark
1
@deepee1 - 我认为问题已经足够清晰了。如果我在问题中加入代码,人们可能会对整个问题感到厌烦。 - Randy Minder
可能是delegates-vs-interfaces-in-c-sharp的重复问题。 - nawfal
4个回答

7
我猜你可以将ActionFunc与包含一个方法的接口进行比较,但不同之处在于你可以提供任何符合参数/返回值要求的ActionFunc>,而使用接口时,提供的对象必须实现该接口。

也许你可以称ActionFunc为“匿名单方法接口”。

但从设计角度来看,你的类模型将是一组没有彼此联系的方块。


2
Action/Func 的方式类似于 Go 中处理接口的方式。它使得两个模块可以有效地没有共同的依赖(除了参数类型)。不过,并不是我会在每种情况下都推荐使用 Action/Func。 - Sebastian Graf

4
我必须承认,这个问题让我有点困惑。像@deepee一样,我认为在这里提供一个代码示例会更好,以便说明您为什么认为需要使用一种方法而不是另一种方法。
我感到困惑的原因是,我本不会想要问这个问题,因为它们具有不同的用途。接口主要用于多态性,以便可以以相同的方式处理不同的实现。
Jon Skeet有一个很好的例子来使用Func和Action。
接口允许您这样做:
IAnimal animal = AnimalFactory.GetAnimal();
animal.Run();

使用上述代码,您不知道或关心它是什么动物。您只知道它可以奔跑,您希望它奔跑。更重要的是,调用者不知道动物如何奔跑。这就是Action和接口/多态之间的区别。执行某些操作的逻辑在具体类中。
当调用者已知实际逻辑时,Action将允许您对每个实例执行相同的操作,而不是让每个具体实例执行某些操作:
animals.ForEach(x => x.Run());

或者:

animals.ForEach(x => /* do something completely different here */);

以上代码行是一种行为,只有调用者决定发生什么,而不是通过简单调用实例上的方法来委托逻辑

它们解决不同的问题,因此我很想知道人们如何认为在某些情况下它们是可替换的。


谢谢。在某些情况下,将不同的实现封装在一个Func中,而不是接口方法中,是否可以同样有效地处理?然后,执行Func而不是调用接口方法。 - Randy Minder
1
信息专家是调用方还是多态具体实例?如果接口的每个具体实例都有自己的逻辑,那么函数将不起作用,因为它在具体实例之外定义。如果调用方知道逻辑,那么函数可以使用。 - Bob Horn
1
你展示的是一个具有多个方法的接口示例。如果IAnimal除了public void Run()之外没有其他内容,那么它实际上并没有什么用处。它不返回任何东西,如果Run具有副作用,则这些副作用是不可见的。IAnimal之所以有用,是因为它会引起通过其他方法可见的副作用,例如PositionStamina。委托只真正替换具有单个方法的接口。 - Servy
+1 如果您遵循动物主题并用言语表达我留在代码中的意思,那就更好了 :) - Shark

4
如果实现非常简短(一两行),尤其是需要使用局部变量(闭包),则应该使用代理和lambda表达式。

短实现并不适用于 Func 和 Action,对吗? - Randy Minder
@RandyMinder:你是什么意思? - SLaks
抱歉,我是在回应你关于委托中代码行数的评论。我认为函数中的代码行数并不重要,对吗? - Randy Minder
@RandyMinder:没错;这一点也不重要。但是,如果它超过一两行,调用代码看起来会很丑陋。 - SLaks

1

当您不关心正在使用哪种对象时,可以使用接口...

让我们来看一个教科书例子

公共类动物;

public class Dog : Animal, IRunningAnimal { }
public class Cheetah : Animal, IRunningAnimal { }
public class Fish : Animal, ISwimmingAnimal { }
public class Gator : Animal, ISwimmingAnimal, IRunningAnimal { }

public interface IRunningAnimal 
{
    public void Run();
}

public interface ISwimmingAnimal
{
    public void Swim();
}

public abstract class Animal
{
    /// ...
    public abstract void Move();
}

然后在代码的某个地方...

RunningAnimal runner = getAnimal();
//make him run
runner.Run();

每只奔跑的动物可能以不同的方式奔跑,但它们都能奔跑。

或者更好地说

if(getAnimal() instanceof RunningAnimal) getAnimal().Run();
else getAnimal().Move();

是的,我刚注意到。另一方面,我会学习Java和C#接口之间的区别 :) - Shark
直接从一本Java编程书中来的? :) - Randy Minder
4
最后一段代码很糟糕。你永远不应该根据对象的类型去做if/switch这样的操作。在这种情况下,你应该只需要一个名为"moveQuickly()"的方法,然后如果动物能够奔跑,就在内部调用run()方法,否则默认使用move()方法即可。 - mike
没问题,这些都与问题无关。 - mike
1
@Shark 接口在C#中与Java中的作用相同。委托可以被视为接口应用的子集,也就是“命令模式”。任何可以使用委托实现的东西都可以改用接口模型来实现,只是接口模型需要更多的代码。委托是一种方便的机制,你永远不需要它们,只是它们让事情变得更容易。就像属性和很多getter和setter一样,它们只需更少的代码即可实现,但这并不意味着属性将替换所有方法 - Servy
显示剩余5条评论

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