C #에서 익명 메서드에 yield 문을 포함 할 수없는 이유는 무엇입니까?
나는 다음과 같은 일을하는 것이 좋을 것이라고 생각했습니다 (람다가 수익률을 반환하는 것과 함께).
public IList<T> Find<T>(Expression<Func<T, bool>> expression) where T : class, new()
{
IList<T> list = GetList<T>();
var fun = expression.Compile();
var items = () => {
foreach (var item in list)
if (fun.Invoke(item))
yield return item; // This is not allowed by C#
}
return items.ToList();
}
그러나 익명의 방법으로 yield를 사용할 수 없다는 것을 알았습니다. 이유가 궁금합니다. 항복 문서는 그냥이 허용되지 않습니다 말한다.
허용되지 않았기 때문에 List를 만들고 항목을 추가했습니다.
Eric Lippert는 최근에 어떤 경우에 생산량이 허용되지 않는 이유에 대한 일련의 블로그 게시물을 작성했습니다.
EDIT2 :
- 파트 7 (이 질문은 나중에 게시되었으며 구체적으로이 질문을 다룹니다)
아마 거기에서 답을 찾을 수있을 것입니다 ...
EDIT1 : 이것은 Abhijeet Patel의 의견에 대한 Eric의 답변에서 Part 5의 의견에 설명되어 있습니다.
Q :
에릭,
또한 익명 메서드 또는 람다 식 내에서 "수익"이 허용되지 않는 이유에 대한 통찰력을 제공 할 수 있습니까?
ㅏ :
좋은 질문. 익명의 반복자 블록을 갖고 싶습니다. 로컬 변수에 대해 닫히는 작은 시퀀스 생성기를 제자리에서 직접 만들 수 있다면 정말 멋질 것입니다. 그렇지 않은 이유는 간단합니다. 이점이 비용보다 중요하지 않습니다. 시퀀스 생성기를 제자리에 만드는 놀라운 점은 실제로 큰 계획에서 매우 작으며 명목상의 방법은 대부분의 시나리오에서 충분히 잘 작동합니다. 따라서 이점은 그다지 매력적이지 않습니다.
비용이 큽니다. 반복자 재 작성은 컴파일러에서 가장 복잡한 변환이고 익명 메서드 재 작성은 두 번째로 복잡합니다. 익명 메서드는 다른 익명 메서드 안에있을 수 있고 익명 메서드는 반복기 블록 안에있을 수 있습니다. 따라서 먼저 모든 익명 메서드를 다시 작성하여 클로저 클래스의 메서드가되도록합니다. 이것은 컴파일러가 메서드에 대해 IL을 방출하기 전에 수행하는 두 번째 마지막 작업입니다. 이 단계가 완료되면 반복기 재 작성기는 반복기 블록에 익명 메서드가 없다고 가정 할 수 있습니다. 그들은 모두 이미 다시 작성되었습니다. 따라서 반복기 재 작성기는 실현되지 않은 익명 메서드가있을 수 있다는 걱정없이 반복기를 재 작성하는 데 집중할 수 있습니다.
또한 반복자 블록은 익명 메서드와 달리 "중첩"되지 않습니다. 반복기 재 작성기는 모든 반복기 블록이 "최상위 수준"이라고 가정 할 수 있습니다.
익명 메서드가 반복기 블록을 포함하도록 허용되면 두 가정이 모두 창 밖으로 나갑니다. 익명 메서드를 포함하는 반복기 블록을 포함하는 익명 메서드를 포함하는 익명 메서드를 포함하는 반복기 블록을 가질 수 있습니다. 이제 중첩 된 반복기 블록과 중첩 된 익명 메서드를 동시에 처리 할 수있는 재 작성 패스를 작성하여 가장 복잡한 두 알고리즘을 훨씬 더 복잡한 하나의 알고리즘으로 병합해야합니다. 디자인, 구현 및 테스트가 정말 어려울 것입니다. 우리는 그렇게 할만큼 똑똑합니다. 여기에 똑똑한 팀이 있습니다. 그러나 우리는 "갖는 것이 좋지만 필요하지 않은"기능에 대해 큰 부담을지고 싶지 않습니다. -에릭
Eric Lippert has written an excellent series of articles on the limitations (and design decisions influencing those choices) on iterator blocks
In particular iterator blocks are implemented by some sophisticated compiler code transformations. These transformations would impact with the transformations which happen inside anonymous functions or lambdas such that in certain circumstances they would both try to 'convert' the code into some other construct which was incompatible with the other.
As a result they are forbidden from interaction.
How iterator blocks work under the hood is dealt with well here.
As a simple example of an incompatibility:
public IList<T> GreaterThan<T>(T t)
{
IList<T> list = GetList<T>();
var items = () => {
foreach (var item in list)
if (fun.Invoke(item))
yield return item; // This is not allowed by C#
}
return items.ToList();
}
The compiler is simultaneously wanting to convert this to something like:
// inner class
private class Magic
{
private T t;
private IList<T> list;
private Magic(List<T> list, T t) { this.list = list; this.t = t;}
public IEnumerable<T> DoIt()
{
var items = () => {
foreach (var item in list)
if (fun.Invoke(item))
yield return item;
}
}
}
public IList<T> GreaterThan<T>(T t)
{
var magic = new Magic(GetList<T>(), t)
var items = magic.DoIt();
return items.ToList();
}
and at the same time the iterator aspect is trying to do it's work to make a little state machine. Certain simple examples might work with a fair amount of sanity checking (first dealing with the (possibly arbitrarily) nested closures) then seeing if the very bottom level resulting classes could be transformed into iterator state machines.
However this would be
- Quite a lot of work.
- Couldn't possibly work in all cases without at the very least the iterator block aspect being able to prevent the closure aspect from applying certain transformations for efficiency (like promoting local variables to instance variables rather than a fully fledged closure class).
- If there was even a slight chance of overlap where it was impossible or sufficiently hard to not be implemented then the number of support issues resulting would likely be high since the subtle breaking change would be lost on many users.
- It can be very easily worked around.
In your example like so:
public IList<T> Find<T>(Expression<Func<T, bool>> expression)
where T : class, new()
{
return FindInner(expression).ToList();
}
private IEnumerable<T> FindInner<T>(Expression<Func<T, bool>> expression)
where T : class, new()
{
IList<T> list = GetList<T>();
var fun = expression.Compile();
foreach (var item in list)
if (fun.Invoke(item))
yield return item;
}
Unfortunately I don't know why they didn't allow this, since of course it's entirely possible to do envision how this would work.
However, anonymous methods are already a piece of "compiler magic" in the sense that the method will be extracted either to a method in the existing class, or even to a whole new class, depending on whether it deals with local variables or not.
Additionally, iterator methods using yield
is also implemented using compiler magic.
My guess is that one of these two makes the code un-identifiable to the other piece of magic, and that it was decided to not spend time on making this work for the current versions of the C# compiler. Of course, it might not be a concious choice at all, and that it just doesn't work because nobody thought to implement it.
For a 100% accurate question I would suggest you use the Microsoft Connect site and report a question, I'm sure you'll get something usable in return.
I would do this:
IList<T> list = GetList<T>();
var fun = expression.Compile();
return list.Where(item => fun.Invoke(item)).ToList();
Of course you need the System.Core.dll referenced from .NET 3.5 for the Linq method. And include:
using System.Linq;
Cheers,
Sly
Maybe its just a syntax limitation. In Visual Basic .NET, which is very similar to C#, it is perfectly possible while awkward to write
Sub Main()
Console.Write("x: ")
Dim x = CInt(Console.ReadLine())
For Each elem In Iterator Function()
Dim i = x
Do
Yield i
i += 1
x -= 1
Loop Until i = x + 20
End Function()
Console.WriteLine($"{elem} to {x}")
Next
Console.ReadKey()
End Sub
Also note the parentheses ' here
; the lambda function Iterator Function
...End Function
returns an IEnumerable(Of Integer)
but is not such an object itself. It must be called to get that object.
The converted code by [1] raises errors in C# 7.3 (CS0149):
static void Main()
{
Console.Write("x: ");
var x = System.Convert.ToInt32(Console.ReadLine());
// ERROR: CS0149 - Method name expected
foreach (var elem in () =>
{
var i = x;
do
{
yield return i;
i += 1;
x -= 1;
}
while (!i == x + 20);
}())
Console.WriteLine($"{elem} to {x}");
Console.ReadKey();
}
I strongly disagree to the reason given in the other answers that it's difficult for the compiler to handle. The Iterator Function()
you see in the VB.NET example is specifically created for lambda iterators.
In VB, there is the Iterator
keyword; it has no C# counterpart. IMHO, there is no real reason this is not a feature of C#.
So if you really, really want anonymous iterator functions, currently use Visual Basic or (I haven't checked it) F#, as stated in a comment of Part #7 in @Thomas Levesque's answer (do Ctrl+F for F#).
'program story' 카테고리의 다른 글
"입력"이벤트에서 백 스페이스 및 del을 감지합니까? (0) | 2020.09.19 |
---|---|
컴파일 타임에 C 문자열의 길이를 계산합니다. (0) | 2020.09.19 |
'IEnumerable'유형의 ViewData 항목이 없습니다. (0) | 2020.09.19 |
자바 스크립트 함수 범위 지정 및 호이 스팅 (0) | 2020.09.19 |
angular.js에서 HTML5 pushstate 사용 (0) | 2020.09.19 |