4

What is the correct way to check if an IEnumerable<T> is generated by the yield keyword ?

Sample :

public IEnumerable<int> GetMeSomeInts() 
{
   // Unknown implementation
}

Somewhere :

IEnumerable<int> someInts = GetMeSomeInts() ;

if (someInts is generatedbyayield) // <- What should be this condition ? 
      someInts = someInts.ToList() ; 
Pablo Honey
  • 1,074
  • 1
  • 10
  • 23
  • 5
    Why in the world would you want to check if an `IEnumerable` is created by `yield return`? Do you actually want to know if the `IEnumerable` is already materialized? – Yuval Itzchakov Dec 09 '15 at 16:18
  • 1
    Don't think you can.... not programmatically anyway. – BG100 Dec 09 '15 at 16:19
  • 2
    Indeed. You shouldn't care. What would you want to happen if `GetMeSomeInts` itself wasn't implemented by an iterator block, but called something else that was? – Jon Skeet Dec 09 '15 at 16:19
  • 3
    @BG100: Well, there are heuristics you could use that would be accurate for anything not deliberately *trying* to fool the system... but it's still a bad idea. – Jon Skeet Dec 09 '15 at 16:20
  • 1
    @JonSkeet: This maybe is not the place to discuss this, but I'm very intrigued about what you mean by this... – BG100 Dec 09 '15 at 16:22
  • [This](http://programmers.stackexchange.com/a/184382/132566) may help. – Yuval Itzchakov Dec 09 '15 at 16:23
  • 1
    it's just to avoid using IEnumerable multiple times if the yield implementation cost a lot – Pablo Honey Dec 09 '15 at 16:28
  • 1
    You could check if it's a collection or not: `public class ExtensionMethods { public static bool IsCollection(this IEnumerable seq) { return seq is ICollection; } }` – Tim Schmelter Dec 09 '15 at 16:29
  • 2
    @PabloHoney There are all sorts of IEnumerables that don't use `yield` that would be *very* expensive if iterated multiple times (for example, any EF query). If you accept an `IEnumerable` that doesn't come from a known source you should never iterate it multiple times, because there simply is no way to know how expensive it'll be. – Servy Dec 09 '15 at 16:42
  • @YuvalItzchakov It was also for curiosity. I think marking it as duplicate of "How to materialize" is not completly relevant as it doesn't resolve my primary intent. – Pablo Honey Dec 09 '15 at 16:43
  • 1
    what should be `GetMeSomeInts().GetType()` if GetSomeInts is an iterator block ? – Pablo Honey Dec 09 '15 at 16:52
  • 2
    @PabloHoney First of all check that this `IEnumerable` is not collection as @TimSchmelter suggested. Then you could check the type of this object. Check if it has the `NestedPrivate`, `Sealed`, `BeforeFieldInit` attrubtes. Then check the `IsConstructedGenericType` property (it should be `false` even for generics). Additionaly, you could also check the name whether it has some weird format `YourClassName+d__0`. I'm not sure whether this approach works in 100% cases. It requires more elaborated research. – Sergii Zhevzhyk Dec 09 '15 at 17:13
  • Continuing @Servy's comment, it is possible that an `IEnumerable` returns different results on each iteration (it is not forbidden, and there are examples such as queries). So you should strive to iterate only once, unless the `IEnumerable` is specifically restricted to allow it. – Pablo H Feb 25 '19 at 22:57
  • @YuvalItzchakov: In my case, I needed to know if an `IEnumerable<>` was generated by `yield`, because such an `IEnumerable<>` can't be remoted, i.e. it doesn't inherit from `MarshalByRefObject` nor is it a simple `Serializable` object. – ulatekh Sep 20 '22 at 17:00

2 Answers2

7

The state machine created by the yield keyword was not designed to be "detectable". If you find a way to detect it, you will have to rely on some implementation-specific hints (such as a specific pattern of the type name; some examples are given in the comments of your question), which are not part of the C# spec and, thus, might change at any time.

Thus, there is no correct way to check if an IEnumerable<T> is generated by the yield keyword. I would argue that the correct way is not to check. That's what interfaces are for: They hide the implementation.


Since you did not mention why you want to find out whether the IEnumerable was generated by the yield keyword, I will make a wild guess and assume that what you actually wanted to ask was:

How can I materialize an IEnumerable if it has not been materialized yet?

That question has been answered already:

Community
  • 1
  • 1
Heinzi
  • 167,459
  • 57
  • 363
  • 519
  • 2
    The accepted answer is checking if it's `IList`, better to check if it's `ICollection` which is the parent interface. – Tim Schmelter Dec 09 '15 at 16:32
  • @TimSchmelter That depends on what you need to do with it. – Servy Dec 09 '15 at 16:43
  • 1
    @Servy: no, this question just wants to know how to check if a sequence is already materialized. – Tim Schmelter Dec 09 '15 at 23:49
  • @TimSchmelter He wants to know if the sequence is materialized so that he can do something with it. We have no idea what that something is, and what operations he might need of this object to do it. – Servy Dec 10 '15 at 01:50
  • @ChrisMarisic: You are right, solving the (assumed) underlying problem without addressing the problem as stated literally in the question is bad style. I have added an introductory paragraph to explain *why* I think that the answer to the original question is "There is no correct way". I have also unclosed the question, so feel free to add an answer yourself. – Heinzi May 20 '16 at 20:43
  • @Heinzi you are certainly correct that they made it very difficult to determine the state machine is the enumerable. However the really weird part, `someInts.GetType().IsGenericType` returns false even though we work with the type as `IEnumerable` and looking at the object in memory it looks like the type is actually `Yielded` yet is not generic. http://stackoverflow.com/a/2319617/37055 is how much code it takes to reliably determine whether an object is `IEnumerable` or not – Chris Marisic May 23 '16 at 19:05
  • @ChrisMarisic: Since `T = MyType` is known at compile time, there would be no *need* for the state machine to be generic: `public class C : IEnumerable { ... }` is perfectly valid, and `C` is *not* a generic class. – Heinzi May 23 '16 at 19:29
  • With you expressing it like that, not thinking of it as directly implementing the interface was probably one of my stumbling blocks – Chris Marisic May 23 '16 at 19:44
2

I wouldn't necessarily say it's the correct way, but A way is:

someInts.GetType().Name.Contains("<Yielded>")

This is probably fairly reliable since this name is very abnormal. Since you're relying on deep internals it could change between any .NET release, although i feel it probably wouldn't/hasn't.

If you use this, make sure to keep a unit test to sanity check it. Such that if the internals do change, your unit test will fail on the build server.

Chris Marisic
  • 32,487
  • 24
  • 164
  • 258