为什么方法不能像func/action委托那样在语法上工作?

6

回家的路上,我想到了一个主意:创建能够允许在c#中使用一些不错语法的Func/Action扩展。

理论上的例子... 创建各种Func/Action的排列方式的扩展,使您可以计时方法的执行。

然而,当我回到家并尝试一个示例时,我发现这是不可能的。我相信这是c#中的一个缺陷/不一致性。委托和方法在理论上是相同的。

public static class Extensions
{
    public static void Time(this Action action)
    {
        // Logic to time the action
        action();
    }
}

public class Example
{
    public void Main()
    {
        Action action = RunApp;
        Action actionLambda = () => { };
        Action actionDelegate = delegate () { };

        Extensions.Time(RunApp); // Works
        Extensions.Time(() => { }); // Works
        Extensions.Time(delegate() { }); // Works
        Extensions.Time(action); // Works
        Extensions.Time(actionLambda); // Works
        Extensions.Time(actionDelegate); // Works

        action.Time(); // Works
        actionLambda.Time(); // Works
        actionDelegate.Time(); // Works

        ((Action) RunApp).Time(); // Works
        ((Action) delegate () { }).Time(); // Works
        ((Action) (() => { })).Time(); // Works

        // These should all be the same! 

        RunApp.Time(); // No good: "Example.RunApp() is a method which is not valid in the given context"
        () => { }.Time(); // No good: Operator '.' cannot be applied  to operand of type 'lambda expression'"
        (() => { }).Time(); // No good: Operator '.' cannot be applied  to operand of type 'lambda expression'"
        delegate() { }.Time(); // No good: "Operator '.' cannot be applied operand of the type 'anonymous method'"
    }

    public void RunApp()
    {
        // Stuff...
    }
}

我知道Func/Action是相对于委托和方法组而言较新的添加,但它们为什么不能完全具备相同的功能呢?


1
Action封装了一个方法,所以,就像你的代码所示,你必须明确地告诉它将某个东西封装为一个Action。 - Keith Nicholas
1
你需要将最后一行改为 ((Action)(() => { })).Time(); - Keith Nicholas
将其添加为另一个“工作”,并重置原始内容。 - smdrager
什么样的回答能满足你的“为什么”?规范参考?语言设计团队中某个人的电子邮件谈论这个问题?在这里不清楚你想要得到什么样的答案。为什么方法在语法上应该像委托一样工作呢? - Will Ray
很高兴你问了。当我结束这个问题时,我意识到它有点修辞。但是在编程中,当事情似乎“应该”时,通常从下面(框架)有明确的答案完美地回答了为什么。我们都希望事情按照某种方式工作,但我会接受“这就是为什么在c#规范中,你应该提出一个功能请求。” 我喜欢这个网站的原因之一是,通常当像这样提交问题时,它会得到那些有能力回答的人的回答。 - smdrager
非常感谢 @Eric Lippert 对答案的贡献。相关问题和问题提供了很多上下文。目前似乎有足够的时间来回答这个问题。我希望答案在8个月之前就给出,因为这可能意味着C#社区还有另一个答案。从相关答案中可以看出,以下内容是有争议的:对于Lambda、组方法和null,如果可以推断类型(如其他地方所示),则应该使用该类型。如果不能,请像其他模棱两可的引用一样抛出异常。无论是否是Linq基本操作,这都是不可预测的行为。 - smdrager
1个回答

0

Action 可以简单地封装委托,无需显式声明您的委托。在 C# 中,委托是捕获方法作为类型的语言机制。类型是您需要创建扩展方法的必备条件...所以,回到简化的委托...

 public static class Extensions
    {
        public delegate void Del();
        public static void Time(this Del action)
        {
            // Logic to time the action
            action();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {            
            ((Extensions.Del)(() => { })).Time();
        }
    }

语言不会自动将方法转换为委托,您需要明确地这样做。方法本身不是一种类型,委托是捕获它们的机制。从根本上讲,在C#中,委托与方法不同。这是C#对具有类型安全指向方法的解决方案。

另一件要记住的事情是你可以做:

 public static class Extensions
    {
        public delegate void Del();
        public delegate void Del2();
        public static void Time(this Del action)
        {
            // Logic to time the action
            action();
        }
        public static void Time(this Del2 action)
        {
           // Logic to time the action
           action();
        }


        public static void Time(this Action action)
        {
            // Logic to time the action
            action();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {            

            ((Extensions.Del)(() => { })).Time();
            ((Extensions.Del2)(() => { })).Time();
            ((Action)(() => { })).Time();

            (() => { })).Time();   // no way to know what type this should be coerced to

        }        
    }

现在你的方法可以被捕获为许多不同的类型。


感谢您花时间思考这个问题,但您的答案并没有回答它。为什么要在扩展类中创建 Del 和 Del2? - smdrager
重点是,它们只是可以捕获方法的类型......您可以随意创建任意数量的它们。扩展方法适用于类型。从方法到类型没有直接路径,因为一个方法可以被许多不同的类型所捕获。 - Keith Nicholas
在所规定的语法中存在一对一的映射。一个类和命名空间中不能有多个描述给定名称的操作的方法。 - smdrager
但是任何方法都可以匹配框架中定义的任何兼容委托,包括Action。那么为什么要将其转换为Action,然后调用您的扩展方法呢? - Keith Nicholas
上面的代码只是一个委托的包装器。我展示了多个委托(和Action),它们映射到相同类型的方法(void void),这意味着没有简单的方法可以确定应该调用哪个Time扩展方法。 - Keith Nicholas
显示剩余3条评论

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