Изменяемая оболочка типов значений для передачи в итераторы - PullRequest
5 голосов
/ 26 июня 2009

Я пишу итератор, который должен передавать изменяемое целое число.

public IEnumerable<T> Foo(ref int valueThatMeansSomething)
{
    // Stuff

    yield return ...;
}

Это выводит меня из строя "Ошибка 476 Итераторы не могут иметь параметры ref или out".

Что мне нужно, так это целочисленное значение, которое должно быть изменено в итераторе и может использоваться вызывающей стороной итератора. Другими словами, все, что звонит Foo() выше, хочет знать конечное значение valueThatMeansSomething, и Foo() может использовать его само. Действительно, я хочу, чтобы целое число было ссылочным типом, а не типом значения.

Единственное, о чем я могу думать, это написать класс, который инкапсулирует мое целое число и позволяет мне изменять его.

public class ValueWrapper<T>
    where T : struct
{
    public ValueWrapper(T item)
    {
        this.Item = item;
    }

    public T Item { get; set; }
}

Итак:

ValueWrapper<int> w = new ValueWrapper<int>(0);
foreach(T item in Foo(w))
{
    // Do stuff
}

if (w.Item < 0) { /* Do stuff */ }

Есть ли какой-либо класс или механизм для обработки этого уже в BCL? Есть ли недостатки с ValueWrapper<T>, предложенные выше?

(Мое фактическое использование более сложное, чем в примере выше, поэтому обработка переменной внутри моего цикла foreach, которая вызывает Foo(), не вариант. Точка.)

Ответы [ 3 ]

5 голосов
/ 27 июня 2009

Если вам нужно только написать значение, тогда другой метод будет:

public IEnumerable<whatever> Foo(Action<int> setter) { ... }

int value = 0;
foreach(var x in Foo(x => {value=x;}) { ... }

По совпадению, я сделаю серию статей о причинах, по которым в моем блоге так много глупых ограничений на блоки итераторов в июле. "Почему нет ссылочных параметров?" будет в начале серии.

http://blogs.msdn.com/ericlippert/archive/tags/Iterators/default.aspx

4 голосов
/ 26 июня 2009

Нет, я почти уверен, что в BCL нет ничего, что могло бы сделать это. Я думаю, ваш лучший вариант - именно то, что вы предложили. Реализация ValueWrapper действительно не должна быть более сложной, чем то, что вы предложили.

Конечно, это не гарантирует поточно-ориентированную работу, но если вам нужно, вы можете просто преобразовать автоматическое свойство в стандартное свойство с резервной переменной и пометить поле как volatile ( чтобы обеспечить актуальность значения в любое время).

0 голосов
/ 26 августа 2012

Я давно думал, что у BCL действительно должен быть класс и интерфейс, подобный следующему:

public delegate void ActByRef<T1,T2>(ref T1 p1);
public delegate void ActByRefRef<T1,T2>(ref T1 p1, ref T2 p2);
public interface IReadWriteActUpon<T>
{
  T Value {get; set;}
  void ActUpon(ActByRef<T> proc);
  void ActUpon<TExtraParam>(ActByRefRef<T, TExtraParam> proc, 
                           ref TExtraparam ExtraParam);
}

public sealed class MutableWrapper<T> : IReadWrite<T>
{
  public T Value;
  public MutableWrapper(T value) { this.Value = value; }
  T IReadWriteActUpon<T>.Value {get {return this.Value;} set {this.Value = value;} }
  public void ActUpon(ActByRef<T> proc)
  {
    proc(ref Value);
  }
  public void ActUpon<TExtraParam>(ActByRefRef<T, TExtraParam> proc, 
                           ref TExtraparam ExtraParam)
  {
    proc(ref Value, ref ExtraParam);
  }
}

Хотя многие люди инстинктивно оборачивают поля в авто-свойствах, они часто позволяют получить более чистый и эффективный код, особенно при использовании типов значений. Во многих ситуациях увеличенная инкапсуляция, которую можно получить с помощью свойств, может стоить затрат на эффективность и семантику, но когда все назначение типа заключается в том, чтобы быть объектом класса, состояние которого полностью доступно и изменчиво, такая инкапсуляция является контрпродуктивной.

Интерфейс включен не потому, что многие пользователи MutableWrapper<T> хотели бы использовать интерфейс вместо этого, а скорее потому, что IReadWriteActUpon<T> может быть полезен в различных ситуациях, некоторые из которых повлекут за собой инкапсуляцию, и кто-то, кто имеет экземпляр MutableWrapper<T>, возможно, пожелает передать его в код, предназначенный для работы с данными, инкапсулированными в интерфейсе IReadWriteActUpon<T>.

...