views:

62

answers:

5

There is some code that I'm trying to convert from IList to IEnumerable:

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

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

The problem is, SetupSomething() comes from the base class and uses:

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

yield ends up creating MoveNext(), MoveNext() calls SetupSomething(), and MoveNext() does not have the [Something(123)] attribute.

I can't change the base class, so it appears I am forced to stay with IList or implement IEnumerable manually (and add the attribute to MoveNext()).

Is there any other way to make yield work in this scenario?

+3  A: 

You can't use iterators (yield) if you require the stack frame functionality. As you've discovered, this rewrites your method into a custom class that implements IEnumerable<T>.

However, you can easily just rework this to:

[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;
}

You lose the deferred execution of the iterator, but it will work properly.

Reed Copsey
Right, currently it's creating a list similar to what you have and I was trying to use deferred execution with IEnumerable/yield.
Nelson
@Nelson: Then you either need to do this (better for maintainability) or split into 2 methods (better if deferred execution required), or implement yourself(I'd avoid - no real advantage) - unfortunately, those are the choices.
Reed Copsey
+1  A: 

You can wrap the method in another method that does all required preprocessing:

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

private IEnumerable<Foo> GetAllFoosInternal()
{
    DataReader dr = RunSomething();
    while (dr.Read())
    {
        yield return Factory.Create(dr);
    }
}
dtb
You're right. I checked RunSomething() and it does not appear to use the stack frame or attributes. It's too bad I would have to split it out like that.
Nelson
I think in this specific case I'm going to avoid splitting it and keeping IList since I'm not dealing with large data sets.
Nelson
+1  A: 

Could you split your method up, like this?

[Something(123)]
public void GetAllFoosHelper()
{
  SetupSomething(); 
}

public IEnumerable<Foo> GetAllFoos() 
{ 
  GetAllFoosHelper();

  DataReader dr = RunSomething(); 
  while (dr.Read()) 
  { 
    yield return Factory.Create(dr); 
  } 
} 
John Fisher
Yup, similar to dtb's example, just reversed.
Nelson
+1  A: 

From your description, it sounds like the problem is that SetupSomething is only looking at the immediate caller on the stack trace. If it looked a little further up (caller's caller) it would find your GetAllFocus call and the desired attribute.

I don't recall off the top of my head, but if yield is creating a MoveNext() implementation only because your class doesn't already implement it, perhaps you can implement your own MoveNext, put the attribute on it, and yield will find and use your MoveNext()? Just a wild guess.

dthorpe
You're probably right. If I implement MoveNext, then I would be implementing IEnumerable manually, which would greatly increase the amount of code. Valid, but not ideal in this case.
Nelson
Generated methods like yield's MoveNext are becoming more common. You'll run into this problem again in the future. Too bad you can't do something to fix the root cause - the stack trace in SetupSomething.
dthorpe
+1  A: 

I'm probably missing something, but I can't make sense of using an attribute here. You might as well have written it like this:

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

A whole heckofalot faster too. And safer, you're dead in the water when the JIT compiler inlines SetupSomething().

Hans Passant
It's actually SetupSomething(params object[] values). However, it could still be SetupSomething(int foo, params object[] values). In this case, SetupSomething() always has to be called, even if there are no params, otherwise RunSomething() fails. I think it's a bad design, but it's what I have to work with.
Nelson
I think I would actually prefer it more like this: SetupParams(params object[] values); RunSomething(int foo); Like I mentioned, SetupSomething() is required only because it reads the attribute, but the actual params are optional. RunSomething() fails if you haven't run SetupSomething() and read the attribute. Seems very backwards to me.
Nelson