admin管理员组文章数量:1316363
I have a method like below.
Product[] productArray = {
new Product { Name="Kayak",Price=275M},
new Product { Name="Lifejacket",Price=375M},
new Product { Name="SoccerBall",Price=475M},
new Product { Name="CornerBall",Price=775M}
};
decimal arrayTotal = productArray.FilterByPrice(700).TotalPrices();
This will call Extension Methods FilterByPrice and TotalPrices.
public static decimal TotalPrices(this IEnumerable<Product?> products)
{
decimal total = 0;
foreach (Product? prod in products)
{
total += prod?.Price ?? 0;
}
return total;
}
public static IEnumerable<Product?> FilterByPrice(this IEnumerable<Product?>productEnum,decimal minimumPrice)
{
foreach(Product? prod in productEnum)
{
if ((prod?.Price ?? 0) >= minimumPrice)
{
yield return prod;
}
}
}
My first question:
decimal arrayTotal = productArray.FilterByPrice(700).TotalPrices();
How the compiler chose the Total Prices method for execute first instead of former method?
My second question: While I debugged the code, I understood TotalPrices method is getting called First, and it goes to below line foreach (Product?prod in products)
After that all of sudden, flow is getting transferred to FilterByPrice method. Then yield will return the value? How?
I want to know how this happened all of a sudden. Is there any internal working I am missing?
I have a method like below.
Product[] productArray = {
new Product { Name="Kayak",Price=275M},
new Product { Name="Lifejacket",Price=375M},
new Product { Name="SoccerBall",Price=475M},
new Product { Name="CornerBall",Price=775M}
};
decimal arrayTotal = productArray.FilterByPrice(700).TotalPrices();
This will call Extension Methods FilterByPrice and TotalPrices.
public static decimal TotalPrices(this IEnumerable<Product?> products)
{
decimal total = 0;
foreach (Product? prod in products)
{
total += prod?.Price ?? 0;
}
return total;
}
public static IEnumerable<Product?> FilterByPrice(this IEnumerable<Product?>productEnum,decimal minimumPrice)
{
foreach(Product? prod in productEnum)
{
if ((prod?.Price ?? 0) >= minimumPrice)
{
yield return prod;
}
}
}
My first question:
decimal arrayTotal = productArray.FilterByPrice(700).TotalPrices();
How the compiler chose the Total Prices method for execute first instead of former method?
My second question: While I debugged the code, I understood TotalPrices method is getting called First, and it goes to below line foreach (Product?prod in products)
After that all of sudden, flow is getting transferred to FilterByPrice method. Then yield will return the value? How?
I want to know how this happened all of a sudden. Is there any internal working I am missing?
Share Improve this question edited Feb 14 at 8:38 Dale K 27.5k15 gold badges58 silver badges83 bronze badges asked Jan 29 at 10:25 stackoverflowresearch456 sstackoverflowresearch456 s 591 silver badge5 bronze badges 04 Answers
Reset to default 9How the compiler chose the
TotalPrices
method for execute first instead of former method?
This is:
- Not about extension classes per se
- Not completely true in terms what is actually happening (but true in terms of the user code).
The thing is yield return
is a syntactic sugar which results in compiler generating special class which encapsulates the user provided logic, exposes it as IEnumerable
/IEnumerator
and substituting the body of the FilterByPrice
with instantiation of that class. I.e FilterByPrice
will look something like the following:
[IteratorStateMachine(typeof(<FilterByPrice>d__1))]
[Extension]
[return: Nullable(new byte[] { 1, 2 })]
public static IEnumerable<Product> FilterByPrice([Nullable(new byte[] { 1, 2 })] IEnumerable<Product> productEnum, decimal minimumPrice)
{
<FilterByPrice>d__1 <FilterByPrice>d__ = new <FilterByPrice>d__1(-2);
<FilterByPrice>d__.<>3__productEnum = productEnum;
<FilterByPrice>d__.<>3__minimumPrice = minimumPrice;
return <FilterByPrice>d__;
}
Full decompilation into C# @sharplab.io
Since the enumeration is lazy here, the user provided code will be executed only when you will start iterating over the generated IEnumerable
/IEnumerator
.
So FilterByPrice
is still called first but your code is not.
You can "overcome" this behavior by moving the yield into local function (can be useful in some fail-fast scenarios):
public static IEnumerable<Product?> FilterByPrice(this IEnumerable<Product?>productEnum, decimal minimumPrice)
{
return Inner(productEnum, minimumPrice);
IEnumerable<Product?> Inner(IEnumerable<Product?>productEnum, decimal minimumPrice)
{
foreach(Product? prod in productEnum)
{
if ((prod?.Price ?? 0) >= minimumPrice)
{
yield return prod;
}
}
}
}
Runnable demo @sharplab.io
See also:
- Execution of an iterator section of the docs
When you call an extension method ExtensionMethod()
on SomeType
instance:
instance.ExtensionMethod()
what you actually do is ExtensionMethod(instance)
. This is because extension methods are static
methods.
When you chain them
instance
.ExtensionMethod()
.ExtensionMethod2()
You do ExtensionMethod2(ExtensionMethod(instance))
.
In your case this means:
decimal arrayTotal = productArray.FilterByPrice(700).TotalPrices();
is really
decimal arrayTotal = TotalPrices(FilterByPrice(productArray,700);
TotalPrices
needs an IEnumerable<Product>
argument which a call to FilterByPrice
should provide. So logically FilterByPrice
is called BEFORE TotalPrices
.
If FilterByPrice
had built the filtered enumerable into a List<Product>
and avoided the yield
keyword, you will see the debugger step into its body before it steps into TotalPrices
.
However, when you use the yield
keyword you instruct the compiler to:
- Create a hidden class/type that implements
IEnumerable/IEnumerator
- Take the body of your current method and put it into the
IEnumerator.MoveNext
method of that new class/type - Rewrite your existing method into essentially:
IEnumerable<Product> FilterByPrice() { var ienuemerable = new HiddenEnumerable(); return ienumerable(); }
Unfortunately, as you have observed, you cannot set/hit ANY breakpoints in this compiler generated method that replaced yours. Personally, I think this is somewhat of a bug especially when setting a break-point at the {
opening bracket of the original method.
Instead, the breakpoints that are set will only be hit when the MoveNext
method is called. This is what prompted your question. TotalPrices
calls the HiddenClass.GetEnumerator().MoveNext
when it foreaches, but you think TotalPrices
is calling the FilterByPrice
method because you are hitting the breakpoints set in its body.
To demonstrate this behavior in a more isolated manner, you can use this code:
var foo = Extensions.FilterByPrice(productArray, 700);
foo.GetEnumerator().MoveNext();
you can't step into the FilterByPrice
method from the first line, but only from the second. Then try using List
instead of a yield
and you would be able to step into from the first line.
For both IEnumerable
/ yield return
and async
/ await
language features, the compiler performs the same transformation of your code.
Your method is moved to a .MoveNext
method on a separate class. Local variables are promoted to class fields. Extra return statements are added to pause you code. And a switch statement, similar to Duff's device, is inserted in your code to resume where execution left off.
Collectively these transformations turn your linear code into a state machine. Your code can be paused and resumed, without needing to write such a state machine manually.
Importantly for your example, calling FilterByPrice
doesn't start the execution of your method. Instead it creates an instance of this state machine, complete with captured argument variables.
It's only when TotalPrices
begins the foreach
loop, and calls IEnumerable.MoveNext
, that your FilterByPrice
method actually starts.
yield may confuse as it returns result of each iteration immediately saving from need to build full result first only to return it.
You could get rid of confusion and make it simpler this same time using Linq. I tested it and it gives this same result. It's my favorite feature in C# I guess.
public static decimal TotalPrices(this IEnumerable<Product?> products)
{
return products.Sum(product => product?.Price ?? 0);
}
public static IEnumerable<Product?> FilterByPrice(this IEnumerable<Product?> productEnum, decimal minimumPrice)
{
return productEnum.Where(product => product?.Price >= minimumPrice);
}
本文标签: cUnderstanding the internal working of an extension class functionalityStack Overflow
版权声明:本文标题:c# - Understanding the internal working of an extension class functionality - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1742003366a2411465.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论