2

Given a MethodBase or a MethodInfo instance how can I check if it represents an iterator method (with yield statements) or a common method (without yield statements)?

I have noticed a IteratorStateMachineAttribute in CustomAttributes property but here it is mentioned that one should not rely on this attribute.

CodeCaster
  • 147,647
  • 23
  • 218
  • 272
Hoborg
  • 891
  • 12
  • 21
  • 1
    See if it returns `IEnumerable`, `IEnumerator`, `IEnumerable` or `IEnumerator`? – Sweeper Jan 17 '20 at 08:43
  • @Sweeper As I understand both an iterator method and "common" method can return `IEnumerable` and `IEnumerator` (at least). So `yield` keyword is just one of (basically two) possible ways to implement a `IEnumerable` or `IEnumerator`. – Hoborg Jan 17 '20 at 08:47
  • 2
    So you are specifically looking for the word `yield` in the method body? Well, that's part of the implementation detail, isn't it? Are you sure you want to depend on implementation details? I don't think other methods _should_ know how other methods are implemented. – Sweeper Jan 17 '20 at 08:50
  • @CodeCaster The answers to the question you have provided ( https://stackoverflow.com/q/34183713/1561966 ) assumes that the method being inspected must be called (the `IEnumerable` instance must be enumerated). While I am seeking for a way to identify an iterator without its invocation. – Hoborg Jan 17 '20 at 11:18
  • @Sweeper I agree with the conception (that other methods should not know how other methods are implemented) but it seems to me that reflection itself breaks this conception. Anyway there are rare cases where one should know an implementation detail, e. g. code inspection and other meta-code considerations. – Hoborg Jan 17 '20 at 11:19
  • I do not see how the duplicate assumes that. The answers give you all the info you need: no, there is no reliable way to detect this. Also, see [Halting problem](https://en.wikipedia.org/wiki/Halting_problem): what if the method starts with `if (someCondition) { return null; }`? – CodeCaster Jan 17 '20 at 11:24
  • @CodeCaster At first it is assumed in the question: "if an IEnumerable is generated by the yield keyword" (obviously it requires the method invocation, may be not the enumertaion though). Second, one answer considers materialization (enumeration) of the `IEnumerable` and another one considers the type of the returned object. However I don't want to call (and enumerate) the method being inspected. Thus those ones are the answers to another question. – Hoborg Jan 17 '20 at 11:38
  • 1
    And the second part of my comment indicates that you cannot know that. Consider `IEnumerable GetFoos() { if (DateTime.Now.Year > 2020) { return null; } else { return SomeOtherPrivateMethodThatMightReturnAnIterator(); }`. You cannot get this information from the declared return type. If it's code analysis you want, you must inspect the source code. You cannot rely on the output for anything. – CodeCaster Jan 17 '20 at 11:40
  • @CodeCaster I completely agreee with that and this is why I am not satisfied by the answers considering invocation of the method being inspected and (moreover) enumerations of the returned object. I am seeking a way to grab some clues about iterator nature of the method from the C# reflection. – Hoborg Jan 17 '20 at 11:49
  • 1
    Reflection looks at metadata, not implementation. If a specific implementation does not emit metadata, you cannot know from reflection that that implementation is used. Iterator methods are not known to emit metadata. And _that_ is explained in the duplicate. Sure, it's a jump from your question, but the answer is the same. – CodeCaster Jan 17 '20 at 11:53
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/206140/discussion-between-hoborg-and-codecaster). – Hoborg Jan 17 '20 at 11:56
  • Sorry, I don't chat. – CodeCaster Jan 17 '20 at 12:15
  • @CodeCaster You have stated that "Iterator methods are not known to emit metadata". But it seems that they DO emit metadata - at least an IteratorStateMachineAttribute. It is proved by an example (target framework .NET 4.6.2) and https://stackoverflow.com/a/37387396/1561966 . And it is interesting to me wether it is the only metadata indicating an iterator and (if yes) what exact conditions it is emitted in. Though this answer - https://stackoverflow.com/a/34183845/1561966 - states that these things "are not part of the C# spec". May be it is a good answer indeed. – Hoborg Jan 17 '20 at 12:49
  • 2
    In what context do you need this, though? Needs like these, insofar they are valid to begin with, are easily served through Roslyn and code analysis. Less so once you start picking apart compiled code. The reason they don't want you to rely on things like `IteratorStateMachineAttribute` is to maintain the freedom of the compiler to do things entirely differently tomorrow, without you having valid grounds to complain. Note that it's a little like asking "how do I detect if the programmer used `??` or an `if` to set this variable" -- if you have reason to care, you probably want the source. – Jeroen Mostert Jan 17 '20 at 13:04

1 Answers1

0

No, it is not possible - since yield is a contextual keyword being processed by the compiler during the compilation process, you cannot check for it directly (see yield contectual keyword) - you can find here a list of contectual keywords the language C# provides.

Definition

A contextual keyword, such as yield, is used to provide a specific meaning in the code, but it is not a reserved word in C#. Some contextual keywords, like partial and where, have special meanings in two or more contexts.

You can find a similar explanation here, why you can't find it out easily with reflection.

But I think that it should be sufficient to check for IEnumerable - because this interface guarantees that an Iterator is available (see iterators). Consider the following method:

IEnumerable<int> Iterator()
{
    for (int i = 0; i < 10; i++)
    {
        yield return i;
    };
}

One way to check if you can do a iteration is:

if (Iterator() is IEnumerable)
    foreach (var item in Iterator())
    {
        Debug.WriteLine(item.ToString());
    }
else
    Debug.WriteLine("Cannot iterate!");

Another way is declarative through Generics:

void Iterate<T>(T iterator)
    where T: System.Collections.IEnumerable
{
    foreach (var item in iterator)
    {
        Debug.WriteLine(item.ToString());
    }
}

You can invoke this via:

Iterate(Iterator());

The difference is, that that via Generics the compiler will check it and give you a compile error before the application runs, while the first example checks it on runtime, i.e. it will compile but fail on runtime.

Of course, you can also iterate through an array, this works perfectly fine (see comment below added by CodeCaster):

var intArray = new int[] {1,2,3};
Iterate(intArray);

You can also check for an enumerator explicitly, i.e.

void Iterate2<T>(T collection)
where T: IEnumerable
{
    var enumerator = collection.GetEnumerator();
    if (enumerator is IEnumerator)
        while (enumerator.MoveNext())
        {
            var item = enumerator.Current;
            Console.WriteLine(item.ToString());
        }
}

I've put the examples together as .NET Fiddle so you can try it out instantly.

Matt
  • 25,467
  • 18
  • 120
  • 187
  • 3
    This is not what the OP means. Your approach will yield (ha-ha) positive for a method that returns `new string[0]` as well, as an array also implements `IEnumerable`. – CodeCaster Jan 17 '20 at 09:04
  • @CodeCaster - Of course, you can iterate through an array - see my update. No joke! – Matt Jan 17 '20 at 09:13
  • 2
    You're missing the point of the question, and trying to mock me in the process of explaining that. The OP wants to know whether a given method, returning an `IEnumerable`, returns that enumerable through the **`yield`** keyword. [These are called "iterator methods"](https://learn.microsoft.com/en-us/dotnet/csharp/iterators), and the OP is trying to detect those. They are not trying to check whether you can actually enumerate over an `IEnumerable`, which you always can do, by design. – CodeCaster Jan 17 '20 at 09:15
  • @CodeCaster - Thank you for the clarification. I have added more details to my answer and why I think it is not possible to get `yield` via reflection. – Matt Jan 17 '20 at 09:35
  • @Matt Thank you for the answer. It seems to me however that the main part of the answer has nothing to do with the question. I have checked the .NET Fiddle you provided and made a try to solve my problem with it but got no luck. Thereby I interpret you answer as "no, it is not possible" (according to the note about contextual keyword processed by a compiler). – Hoborg Jan 17 '20 at 11:44
  • @Hoborg - you're welcome. I have put the part directly related to the answer to the beginning, this way I hope it is better to understand the key part. – Matt Jan 17 '20 at 12:57