Mutable wrapper of value types to pass into iterators
Asked Answered
K

3

5

I'm writing an iterator that needs to pass around a mutable integer.

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

    yield return ...;
}

This nets me "Error 476 Iterators cannot have ref or out parameters".

What I need is this integer value to be modified in the iterator and usable by the caller of the iterator. In other words, whatever calls Foo() above wants to know the end value of valueThatMeansSomething and Foo() may use it itself. Really, I want an integer that is a reference type not a value type.

Only thing I can think of is to write a class that encapsulates my integer and permits me to modify it.

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

    public T Item { get; set; }
}

So:

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

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

Is there any class or mechanism to handle this already in the BCL? Any flaws with ValueWrapper<T> proposed above?

(My actual use is more complicated than the example above so handling the variable inside my foreach loop that calls Foo() is not an option. Period.)

Kaycekaycee answered 26/6, 2009 at 18:1 Comment(0)
R
4

Nope, I'm pretty confident there's nothing existing in the BCL that can do this. Your best option is precisely what you have proposed I think. The implementation of ValueWrapper really need not be any more complicated than what you have proposed.

Of course, it's not guaranteed to be thread-safe, but if you need that you can simply convert the automatic property into a standard one with a backing variable and mark the field as volatile (to insure the value is up-to-date at all times).

Riotous answered 26/6, 2009 at 18:6 Comment(9)
Making a field volatile is not enough to ensure thread safety because writes to arbitrary value types are not guaranteed to be atomic by the C# specification. Volatile does not guarantee atomicity, it just eliminates some compiler-optimization-induced ordering issues.Cellist
@Eric: Yeah, good point. I originally wrote that it guarantees atomicity, but then quickly removed it as I realised this wasn't necessarily the cases.Riotous
To clarify: I presume the right way to go is to declare the field as volatile and use locks?Riotous
If every access to the data is correctly under a lock then it cannot possibly change while you're looking at it. So why would it need to be volatile?Cellist
You tell me. I really don't understand what volatile does with the CLR. The MSDN docs aren't very clear about it either. In fact, you seem to be advocating never to use volatile keyword, because locks are always better. Is this really the case?Riotous
"Volatile" is for writing lock-free threadsafe programs. If you do not know EXACTLY what volatile does and does not do then yes, locks are better. You WILL get lock-free programming wrong if you do not have an extremely detailed understanding of exactly how the processor is allowed to reorder execution of instructions running on different threads.Cellist
"Volatile" is for those situations where you've got a performance problem due to lock contention that you cannot solve by eliminating the contention. (remember, uncontested locks are cheap.) If you can make the algorithm threadsafe and lock free then clearly you will not have the perf problem due to the locks. But to do that, you are running completely without any kind of safety system that protects you from multi-processor reordering optimizations. You have to know what all possible processor optimizations are and whether "volatile" defeats them or not.Cellist
@Eric: Ah, so it's far from a simple matter. I would probably do well to read up on the CLR specification regarding this. Thanks for explaining.Riotous
FYI, the CLI spec and what the CLR implements are a bit different; CLR 2.0 and above implement a stronger memory model than is required by the CLI spec. In particular, double-checked singleton initialization is guaranteed to be correct in CLR 2.0 without volatile, but is not by the CLI. If this stuff really interests you, try Joe Duffy's blog; he is the best expert on all this stuff.Cellist
C
5

If you only need to write the value then another technique would be:

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

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

Coincidentally, I'll be doing a series on the reasons why there are so many goofy restrictions on iterator blocks in my blog in July. "Why no ref parameters?" will be early in the series.

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

Cellist answered 26/6, 2009 at 22:7 Comment(0)
R
4

Nope, I'm pretty confident there's nothing existing in the BCL that can do this. Your best option is precisely what you have proposed I think. The implementation of ValueWrapper really need not be any more complicated than what you have proposed.

Of course, it's not guaranteed to be thread-safe, but if you need that you can simply convert the automatic property into a standard one with a backing variable and mark the field as volatile (to insure the value is up-to-date at all times).

Riotous answered 26/6, 2009 at 18:6 Comment(9)
Making a field volatile is not enough to ensure thread safety because writes to arbitrary value types are not guaranteed to be atomic by the C# specification. Volatile does not guarantee atomicity, it just eliminates some compiler-optimization-induced ordering issues.Cellist
@Eric: Yeah, good point. I originally wrote that it guarantees atomicity, but then quickly removed it as I realised this wasn't necessarily the cases.Riotous
To clarify: I presume the right way to go is to declare the field as volatile and use locks?Riotous
If every access to the data is correctly under a lock then it cannot possibly change while you're looking at it. So why would it need to be volatile?Cellist
You tell me. I really don't understand what volatile does with the CLR. The MSDN docs aren't very clear about it either. In fact, you seem to be advocating never to use volatile keyword, because locks are always better. Is this really the case?Riotous
"Volatile" is for writing lock-free threadsafe programs. If you do not know EXACTLY what volatile does and does not do then yes, locks are better. You WILL get lock-free programming wrong if you do not have an extremely detailed understanding of exactly how the processor is allowed to reorder execution of instructions running on different threads.Cellist
"Volatile" is for those situations where you've got a performance problem due to lock contention that you cannot solve by eliminating the contention. (remember, uncontested locks are cheap.) If you can make the algorithm threadsafe and lock free then clearly you will not have the perf problem due to the locks. But to do that, you are running completely without any kind of safety system that protects you from multi-processor reordering optimizations. You have to know what all possible processor optimizations are and whether "volatile" defeats them or not.Cellist
@Eric: Ah, so it's far from a simple matter. I would probably do well to read up on the CLR specification regarding this. Thanks for explaining.Riotous
FYI, the CLI spec and what the CLR implements are a bit different; CLR 2.0 and above implement a stronger memory model than is required by the CLI spec. In particular, double-checked singleton initialization is guaranteed to be correct in CLR 2.0 without volatile, but is not by the CLI. If this stuff really interests you, try Joe Duffy's blog; he is the best expert on all this stuff.Cellist
R
0

I have long thought that the BCL really should have a class and interface something like the following:

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);
  }
}

Although many people instinctively wrap fields in auto-properties, fields often allow cleaner and more efficient code especially when using value types. In many situations, the increased encapsulation one can gain by using properties may be worth the cost in efficient and semantics, but when the whole purpose of a type is to be a class object whose state is completely exposed and mutable, such encapsulation is counterproductive.

The interface is included not because many users of a MutableWrapper<T> would want to use the interface instead, but rather because an IReadWriteActUpon<T> could be useful in a variety of situations, some of which would entail encapsulation, and someone who has an instance of MutableWrapper<T> might wish to pass it to code which is designed to work with data encapsulated in an IReadWriteActUpon<T> interface.

Richrichara answered 26/8, 2012 at 16:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.