【问题标题】:Virtual IEnumerable<T> that compiles empty编译为空的虚拟 IEnumerable<T>
【发布时间】:2012-06-13 22:02:04
【问题描述】:

我正在创建一个基类,它有一个名为“GetBaseAddresses()”的virtual 方法。它有一个返回类型IEnumerable&lt;Uri&gt;。如果枚举,基类不会yield 任何结果,但派生类可以选择覆盖该方法并返回任意数量的项。

这是基本方法:

public virtual IEnumerable<Uri> GetBaseAddresses() { }

问题是,它不会编译。您必须返回一个值,让编译器满意。所以,因为我想得到一个空的结果,我会直接返回null,对吧?

public virtual IEnumerable<Uri> GetBaseAddresses() { return null; }

问题在于,如果有人对基类的实例执行foreach,他们将崩溃并出现“未设置对象引用...”的运行时错误

所以,回想一下 yield return 关键字对 C# 编译器有一些魔力...我想出了这个编译器技巧(顺便说一句)。

public virtual IEnumerable<Uri> GetBaseAddresses()
{
    if (false) { yield return new Uri(""); }
}

奇怪的是,即使“if (false) { ... }”代码被完全编译掉了——编译器很高兴我满足了“必须返回一个值”的要求,并且完全按照我的意愿去做——是一个可以安全枚举的空结果集。

我的问题是 - 有没有办法在没有我的编译器技巧的情况下做到这一点?

【问题讨论】:

  • 为什么不使用抽象基类和方法?
  • 因为大多数类都有功能......只有几个方法是virtual

标签: c# compiler-construction virtual ienumerable yield-return


【解决方案1】:

使用空数组,或者:

yield break; //end enumeration

从 cmets 中汲取 svick 的想法:

return Enumerable.Empty<Uri>();

这更快,因为Enumerable.Empty 总是返回一个缓存的、预分配的空枚举实例。

【讨论】:

  • 你的想法是对的:你需要使用枚举器特性来让编译器把这个方法当作一个枚举器。
  • Enumerable.Empty() 比空数组好,因为每次都会创建数组,而 Empty 会被缓存。
【解决方案2】:

只需在基方法中返回一个空的可枚举。

    public virtual IEnumerable<Uri> GetBaseAddresses()
    {
        return Enumerable.Empty<Uri>();
    }

或者,如果您的目标是 .NET framework

【讨论】:

  • 所有 3 个答案都很棒 - 你的答案是第一个(并在 .Net 框架中公开了一个专门针对这种情况设计的类)......很好 :)
【解决方案3】:

内置数组支持 IEnumerable,因此您可以使用:

public virtual IEnumerable<Uri> GetBaseAddresses() 
{     
      return new Uri[0];
}

【讨论】:

  • @TimothyKhouri 附录:使用“yeld something”(中断或返回)将始终强制编译器创建一个 IEnumerable 类以返回给调用者。如果它是空的(因为被编译器优化掉或者没有值被 design 和 yield break 没有任何 yeld 返回)那么编译器将简单地创建一个 empty 可枚举类(取查看使用 Reflector 或 ILDasm 生成的代码)。 +1 '因为空数组比这轻得多。
  • 我确实知道(并且想到了)这个选项 - 但我希望避免每次都创建一个新对象。似乎没有办法解决这个问题,所以我将创建一个私有静态只读 _empty 变量并每次都返回它。
  • @TimothyKhouri:拥有静态空数组成员是一种常见的解决方案 - 空数组本质上是不可变的
【解决方案4】:

空方法不起作用的原因是编译器假设如果你不在方法中使用yield关键字,那么你想创建一个普通的方法,而不是一个迭代器。这也是您的if (false) hack 有效的原因。

要正确编写返回空集合的方法,您可以以通常的方式返回空集合:

public virtual IEnumerable<Uri> GetBaseAddresses()
{
    return Enumerable.Empty<Uri>();
}

或者你可以使用yield break:

public virtual IEnumerable<Uri> GetBaseAddresses()
{
    yield break;
}

【讨论】:

    猜你喜欢
    • 2015-10-22
    • 2021-05-29
    • 1970-01-01
    • 1970-01-01
    • 2013-12-10
    • 2012-10-28
    • 1970-01-01
    • 2017-04-24
    • 1970-01-01
    相关资源
    最近更新 更多