【问题标题】:Attribute lost with yield属性随产量而丢失
【发布时间】:2010-04-07 17:29:06
【问题描述】:

我正在尝试将一些代码从 IList 转换为 IEnumerable

[Something(123)]
public IEnumerable<Foo> GetAllFoos()
{
  SetupSomething();

  DataReader dr = RunSomething();
  while (dr.Read())
  {
    yield return Factory.Create(dr);
  }
}

问题是,SetupSomething() 来自基类并使用:

Attribute.GetCustomAttribute(
    new StackTrace().GetFrame(1).GetMethod(), typeof(Something))

yield 最终创建 MoveNext()MoveNext() 调用 SetupSomething(),而 MoveNext() 没有 [Something(123)] 属性。

我无法更改基类,所以看来我不得不继续使用IList 或手动实现IEnumerable(并将属性添加到MoveNext())。

在这种情况下,还有其他方法可以让 yield 工作吗?

【问题讨论】:

    标签: c# reflection attributes yield


    【解决方案1】:

    如果您需要堆栈框架功能,则不能使用迭代器(yield)。正如您所发现的,这会将您的方法重写为实现IEnumerable&lt;T&gt; 的自定义类。

    但是,您可以轻松地将其修改为:

    [Something(123)]
    public IEnumerable<Foo> GetAllFoos()
    {
      SetupSomething();
    
      List<Foo> results = new List<Foo>();
      DataReader dr = RunSomething();
      while (dr.Read())
      {
        results.Add(Factory.Create(dr));
      }
      return results;
    }
    

    你失去了迭代器的延迟执行,但它会正常工作。

    【讨论】:

    • 对,目前它正在创建一个与您所拥有的列表类似的列表,我正在尝试将延迟执行与 IEnumerable/yield 一起使用。
    • @Nelson:那么你要么需要这样做(更好的可维护性)或分成 2 个方法(如果需要延迟执行更好),或者自己实现(我会避免 - 没有真正的优势) -不幸的是,这些都是选择。
    【解决方案2】:

    您可以将该方法包装在另一个方法中,该方法执行所有必需的预处理:

    [Something(123)]
    public IEnumerable<Foo> GetAllFoos()
    {
        SetupSomething();
        return GetAllFoosInternal();
    }
    
    private IEnumerable<Foo> GetAllFoosInternal()
    {
        DataReader dr = RunSomething();
        while (dr.Read())
        {
            yield return Factory.Create(dr);
        }
    }
    

    【讨论】:

    • 你是对的。我检查了 RunSomething() ,它似乎没有使用堆栈框架或属性。太糟糕了,我不得不像那样把它分开。
    • 我认为在这种特定情况下,我将避免拆分它并保留 IList,因为我不处理大型数据集。
    【解决方案3】:

    你能像这样拆分你的方法吗?

    [Something(123)]
    public void GetAllFoosHelper()
    {
      SetupSomething(); 
    }
    
    public IEnumerable<Foo> GetAllFoos() 
    { 
      GetAllFoosHelper();
    
      DataReader dr = RunSomething(); 
      while (dr.Read()) 
      { 
        yield return Factory.Create(dr); 
      } 
    } 
    

    【讨论】:

    • 是的,类似于 dtb 的例子,只是颠倒了。
    【解决方案4】:

    根据您的描述,听起来问题在于 SetupSomething 仅查看堆栈跟踪上的直接调用者。如果它再往上看一点(调用者的调用者),它会找到您的 GetAllFocus 调用和所需的属性。

    我不记得了,但是如果 yield 只是因为你的类还没有实现它而创建了一个 MoveNext() 实现,也许你可以实现你自己的 MoveNext,把属性放在它上面,并且 yield 会找到并使用您的 MoveNext()?只是一个疯狂的猜测。

    【讨论】:

    • 你可能是对的。如果我实现 MoveNext,那么我将手动实现 IEnumerable,这将大大增加代码量。有效,但在这种情况下并不理想。
    • 像 yield 的 MoveNext 这样的生成方法变得越来越普遍。以后你会再次遇到这个问题。太糟糕了,您无法解决根本原因 - SetupSomething 中的堆栈跟踪。
    【解决方案5】:

    我可能遗漏了一些东西,但我无法理解在这里使用属性。你不妨这样写:

    public IEnumerable<Foo> GetAllFoos()
    {
      SetupSomething(123);
      // etc..
    }
    

    也快了一大截。更安全的是,当 JIT 编译器内联 SetupSomething() 时,你就死定了。

    【讨论】:

    • 实际上是 SetupSomething(params object[] values)。但是,它仍然可能是 SetupSomething(int foo, params object[] values)。在这种情况下,必须始终调用 SetupSomething(),即使没有参数,否则 RunSomething() 将失败。我认为这是一个糟糕的设计,但这是我必须使用的。
    • 我想我实际上更喜欢这样:SetupParams(params object[] values); RunSomething(int foo);就像我提到的,SetupSomething() 是必需的,因为它读取属性,但实际参数是可选的。如果您尚未运行 SetupSomething() 并读取属性,则 RunSomething() 将失败。对我来说似乎很倒退。
    猜你喜欢
    • 2017-11-24
    • 1970-01-01
    • 1970-01-01
    • 2020-12-03
    • 2020-05-30
    • 2021-07-16
    • 2014-09-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多