【问题标题】:C# delegate that returns itself返回自身的 C# 委托
【发布时间】:2013-11-30 20:57:41
【问题描述】:

基本上我想要一个可以自行返回的委托。我对下面理论上想要的内容进行了非常简单的简化:

int i =0;
var x = () => 
{
    if (i > 10)
        return null;

    ++i;
    Debug.Log("keep going");
    return x;
};

var y = x;

while ((y = y()) != null) ;

当然,预期的输出是在控制台中看到“继续”10 次。我只是想知道在这种语言中这是否可能(我知道我可以使用 for 循环实现相同的目标)。例如,在 JavaScript 中使用与上面非常相似的代码进行操作很简单,但我无法让编译器与 C# 配合。我认为失败在于无法正确描述“x”(Func<Func<Func... 的类型,你有点想要前向声明之类的东西。

【问题讨论】:

  • 你自己试过了吗
  • 这种代码是否有用?
  • @JulieShannon 是的,我已经尝试了上面的确切代码,但它抱怨“var”(也许我应该提到我在 Unity 中的 Mono 上运行(.NET 2 兼容性),所以也许上面的代码在较新的 C# 运行时中是完全合法的。
  • 这个概念在 C# 中不能直接实现,原因很简单:函数x 的返回类型是什么?只有当类型以某种方式“模糊”时才有可能。你可能想看看这个:stackoverflow.com/questions/2679547/…
  • @lazyberezovsky 使用这种技术的代码在函数式编程中非常常见。例如,您可以有一个解析器函数,它返回解析的下一步:while (parse = parse(stream)) yield return wait_for_more_data; 解析器的状态在 lambda 闭包中被捕获,因此使解析器可暂停变得微不足道。不过,我不想再讨论替代策略,我只是想知道 C# 是否可以处理这个问题。

标签: c# lambda delegates


【解决方案1】:

为了澄清我之前的评论,C# 使用静态类型和“具体化”泛型。这意味着编译器会跟踪泛型类型的每个参数化。 Func<Func<int>> 类型与 Func<Func<Func<int>>> 类型不同。 C# 不支持任何类型的动态递归,更不用说无限参数化了,像Func<Func<... 这样的类型的天真构造将涉及无限递归,这将表现为必须跟踪无限数量的类型。

但是,有一些技巧可以让它发挥作用。它们都涉及以某种方式掩盖函数的返回类型。其他解决方案通过返回弱类型、后期绑定的Delegate 对象来做到这一点。但是,也有一个静态类型的解决方案。您需要做的就是定义自己的非泛型 delegate 对象。

    private delegate InfFunc InfFunc();
    static void Main(string[] args)
    {
        InfFunc f = null;
        int i = 0;
        f = () =>
            {
                if (i > 10) return null;
                i++;
                Debug.WriteLine("Keep going");
                return f;
            };
        var g = f;

        while ((g = g()) != null) ;
        Debug.WriteLine(i);
    }

或者您也可以这样做,尽管您的 .NET 版本不支持 DLR:

    static void Main(string[] args)
    {
        Func<dynamic> f = null;
        int i = 0;
        f = () =>
            {
                if (i > 10) return null;
                i++;
                Debug.WriteLine("Keep going");
                return f;
            };
        var g = f;

        while ((g = g()) != null) ;
        Debug.WriteLine(i);
    }

【讨论】:

  • 啊,太棒了,非常感谢! RE:第二个例子,Func 似乎工作得很好(然后做 = (Func)(f()))。但是是的,第一个例子正是我想要的。
【解决方案2】:

也许不完全是您所希望的,但这可以解决问题:

        int i = 0;
        Func<Delegate> x = null;
        x = () =>
        {
            if (i > 10)
                return null;

            Debug.Log("keep going");
            ++i;
            return x;
        };
        Func<Delegate> y = x;
        while ((y = y() as Func<Delegate>) != null) ;

为了解决Func&lt;Func&lt;... 的无限序列的问题,您可以使用Delegate,然后在调用后将其转换为Func&lt;Delegate&gt;。

【讨论】:

  • 这似乎只循环一次(在 Unity 中使用具有 .NET 2 兼容性的 Mono - 如果这在新版本的 C# 中表现不同,我们深表歉意)
  • 对不起,误读了输出,这实际上工作正常。我选择了另一个类似的答案,只是因为它更早,但我仍然给你投了赞成票。
  • 这很好,但你应该知道DynamicInvoke 引入了额外的开销,这就是我选择演员表的原因。
【解决方案3】:

我认为你能做的最好的就是返回类型Delegate:

int i = 0;
Func<Delegate> f = null;
f = () =>
{
    if (i > 10)
        return null;

    ++i;
    Debug.WriteLine("keep going");

    return f;
};

Delegate y = f;

while ((y = (Delegate)y.DynamicInvoke()) != null)
{

}

【讨论】:

  • 这似乎只循环一次(在 Unity 中使用具有 .NET 2 兼容性的 Mono - 如果这在新版本的 C# 中表现不同,我们深表歉意)
  • 对不起,我误读了输出,这实际上工作正常。谢谢!
【解决方案4】:

我不确定您要做什么,但是:

int i =0;
Func<Delegate> x = null;
x = () => 
{
    if (i > 10)
        return null;

    ++i;
    Console.WriteLine("keep going");
    return x;
};

var y = x;

while (y() != null) ;

【讨论】:

  • y = y() 至关重要,因为 y() 可能会返回不同的函数。例如,如果 (i > 10) return () => { Debug.Log("last");返回空};抱歉没有说得更清楚。
猜你喜欢
  • 2012-04-07
  • 2013-08-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-31
  • 2019-12-23
  • 1970-01-01
相关资源
最近更新 更多