Почему захват смешанной переменной союзной структуры внутри закрытия в операторе использования измените его локальное поведение?
-
09-10-2019 - |
Вопрос
Обновлять: Ну, теперь я ушел и сделал это: я Подал отчет об ошибках с Microsoft Об этом, как я серьезно сомневаюсь, что это правильное поведение. Что сказал, я до сих пор не на 100% уверен, что верить в отношении этот вопрос; так что я вижу, что то, что «правильно» открыто для немного Уровень интерпретации.
Мое чувство состоит в том, что либо Microsoft примет, что это ошибка, либо иначе реагируют, что модификация изменяемой переменной типа Visual Value в пределах using
Заявление составляет неопределенное поведение.
Кроме того, для чего это стоит, у меня есть хотя бы догадка Что касается того, что здесь происходит. Я подозреваю, что компилятор генерирует класс для закрытия, «поднятие» локальной переменной в поле экземпляра этого класса; и так как это в пределах using
блокировать, Это делает поле readonly
. Отказ Как Луки указал в комментарий к другому вопросу, это предотвратит такие вызовы методов, такие как MoveNext
от модификации самой поля (вместо этого они будут влиять на копию).
Примечание. Я укоротил этот вопрос для чтения, хотя он все еще не совсем короткий. Для оригинального (дольше) вопроса во всей его полноте см. В истории редактирования.
Я прочитал, что я считаю, являются соответствующими разделами ЭКМА-334 и не могут найти окончательный ответ на этот вопрос. Сначала я сделаю вопрос, а затем предоставьте ссылку на некоторые дополнительные комментарии для тех, кто заинтересован.
Вопрос
Если у меня есть мультипликационный тип, который реализует IDisposable
, Я могу (1) вызовите метод, который изменяет состояние значения локальной переменной в пределах using
Заявление и код ведет себя как я ожидаю. После того, как я подчиняю вариабельную переменную внутри закрытия в пределах то using
Оператор, однако, (2) модификации на значение больше не видны в локальном объеме.
Это поведение очевидно только в случае, когда переменная захватавается внутри закрытия а также в пределах using
утверждение; Это не очевидно, когда только один (using
) или другое условие (закрытие) присутствует.
Почему захват переменной смежной типы значений внутри закрытия в пределах using
Заявление измените его местное поведение?
Ниже приведены примеры кода, иллюстрирующие элементы 1 и 2. Оба примера будут использоваться следующая демонстрация Mutable
тип значения:
struct Mutable : IDisposable
{
int _value;
public int Increment()
{
return _value++;
}
public void Dispose() { }
}
1. Мутируйте переменную типа значения в пределах using
блокировать
using (var x = new Mutable())
{
Console.WriteLine(x.Increment());
Console.WriteLine(x.Increment());
}
Выходные данные вывода:
0 1
2. Захватывание переменной типа Value внутри закрытия в пределах using
блокировать
using (var x = new Mutable())
{
// x is captured inside a closure.
Func<int> closure = () => x.Increment();
// Now the Increment method does not appear to affect the value
// of local variable x.
Console.WriteLine(x.Increment());
Console.WriteLine(x.Increment());
}
Вышеуказанные выходы кода:
0 0
Дальнейшие комментарии
Было отмечено, что Mono Compiler предоставляет поведение, которое я ожидаю (изменения в значение локальной переменной все еще видны в using
+ Закрывающий корпус). Правильно ли это поведение или нет, неясно для меня.
Для некоторых моих мыслей по этому вопросу см. здесь.
Решение
Это известная ошибка; Мы обнаружили его пару лет назад. Исправление было бы потенциально взломать, и проблема довольно неясна; Это точки против исправления его. Поэтому он никогда не был приоритетным достаточно высоким, чтобы на самом деле его исправить.
Это было в моей очереди потенциальных темов блога пару лет сейчас; Возможно, я должен написать это.
И кстати, ваша предположение относительно механизма, который объясняет ошибку, совершенно точна; Хорошая психическая отладка там.
Итак, да, известная ошибка, но спасибо за отчет независимо от!
Другие советы
Это связано с тем, как генерируются типы закрытия и используются. Там, кажется, является тонкой ошибкой, так как CSC использует эти типы. Например, вот IL генерируется GMC MOSO при вызове MOVENEXT ():
IL_0051: ldloc.3
IL_0052: ldflda valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32> Foo/'<Main>c__AnonStorey0'::enumerator
IL_0057: call instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::MoveNext()
Обратите внимание, что он загружает адрес поля, который позволяет вызовому способу изменять экземпляр типа значения, хранящегося на объекте закрытия. Это то, что я бы считал правильным поведением, и это результаты в списке содержимое перечислено просто нормально.
Вот что генерирует CSC:
IL_0068: ldloc.3
IL_0069: ldfld valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32> Tinker.Form1/'<>c__DisplayClass3'::enumerator
IL_006e: stloc.s 5
IL_0070: ldloca.s 5
IL_0072: call instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::MoveNext()
Таким образом, в этом случае он принимает копию экземпляра значений и вызывающий метод на копии. Не должно не удивить, почему это не дает вам никуда. Вызов Get_Current () аналогично неправильно:
IL_0052: ldloc.3
IL_0053: ldfld valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32> Tinker.Form1/'<>c__DisplayClass3'::enumerator
IL_0058: stloc.s 5
IL_005a: ldloca.s 5
IL_005c: call instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::get_Current()
IL_0061: call void class [mscorlib]System.Console::WriteLine(int32)
С момента состояния перечисления он копирование не было MOVENEXT (), называемого, get_current (), очевидно, возвращает default(int)
.
Короче говоря: CSC кажется багги. Интересно, что MOSO получил это правильно, пока Ms.net нет!
... Я хотел бы услышать комментарии Джона Скета об этой конкретной странности.
В обсуждении с Брайковием в #монов он определил, что спецификация языка C # на самом деле не детализируется как Тип закрывания должен быть реализован, ни как доступ к локалам, которые захватываются в закрытии, должны перевести. Пример реализации в спецификации, кажется, использует метод «копирования», который использует CSC. Следовательно, вывод компилятора можно считать правильным в соответствии с спецификацией языка, хотя я бы утвердовал, что CSC должен, по крайней мере, скопировать локальный обратно в объект закрытия после вызова метода.
Редактировать - это неверно, я не прочитал вопрос достаточно тщательно.
Размещение структуры в закрытие вызывает назначение. Назначения на типов значений приводят к копии типа. Так что происходит, вы создаете новый Enumerator<int>
, а также Current
На этом перечислене вернется 0.
using System;
using System.Collections.Generic;
class Program
{
static void Main(string[] args)
{
List<int> l = new List<int>();
Console.WriteLine(l.GetEnumerator().Current);
}
}
Результат: 0.
Проблема в том, что перечислетель хранится в другом классе, поэтому каждое действие работает с копией перечисления.
[CompilerGenerated]
private sealed class <>c__DisplayClass3
{
// Fields
public List<int>.Enumerator enumerator;
// Methods
public int <Main>b__1()
{
return this.enumerator.Current;
}
}
public static void Main(string[] args)
{
List<int> <>g__initLocal0 = new List<int>();
<>g__initLocal0.Add(1);
<>g__initLocal0.Add(2);
<>g__initLocal0.Add(3);
List<int> list = <>g__initLocal0;
Func<int> CS$<>9__CachedAnonymousMethodDelegate2 = null;
<>c__DisplayClass3 CS$<>8__locals4 = new <>c__DisplayClass3();
CS$<>8__locals4.enumerator = list.GetEnumerator();
try
{
if (CS$<>9__CachedAnonymousMethodDelegate2 == null)
{
CS$<>9__CachedAnonymousMethodDelegate2 = new Func<int>(CS$<>8__locals4.<Main>b__1);
}
while (CS$<>8__locals4.enumerator.MoveNext())
{
Console.WriteLine(CS$<>8__locals4.enumerator.Current);
}
}
finally
{
CS$<>8__locals4.enumerator.Dispose();
}
}
Без лямбда код ближе к тому, что вы ожидаете.
public static void Main(string[] args)
{
List<int> <>g__initLocal0 = new List<int>();
<>g__initLocal0.Add(1);
<>g__initLocal0.Add(2);
<>g__initLocal0.Add(3);
List<int> list = <>g__initLocal0;
using (List<int>.Enumerator enumerator = list.GetEnumerator())
{
while (enumerator.MoveNext())
{
Console.WriteLine(enumerator.Current);
}
}
}
Конкретный IL.
L_0058: ldfld valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32> Machete.Runtime.Environment/<>c__DisplayClass3::enumerator
L_005d: stloc.s CS$0$0001
L_005f: ldloca.s CS$0$0001