2009-06-26 11 views
5

Sto scrivendo un iteratore che deve passare attorno a un intero mutabile.Involucro mutabile di tipi di valori da passare negli iteratori

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

    yield return ...; 
} 

Questo mi cattura con la rete "Errore 476 Iteratori non possono avere rif o fuori dei parametri".

Quello che mi serve è che questo valore intero sia modificato nell'iteratore e utilizzabile dal chiamante dell'iteratore. In altre parole, qualunque chiamata Foo() sopra vuole conoscere il valore finale di valueThatMeansSomething e Foo() può utilizzarlo. In realtà, voglio un intero che è un tipo di riferimento e non un tipo di valore.

L'unica cosa a cui riesco a pensare è scrivere una classe che incapsula il mio intero e mi consente di modificarlo.

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

    public T Item { get; set; } 
} 

Quindi:

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

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

C'è qualsiasi classe o di un meccanismo per gestire questo già nel BCL? Eventuali difetti con ValueWrapper<T> proposti sopra?

(mio uso effettivo è più complicata di quanto l'esempio di cui sopra in modo da gestire la variabile dentro la mia foreach ciclo che chiama Foo() non è un'opzione. Periodo.)

risposta

4

No, io sono abbastanza sicuro non c'è nulla di esistente nel BCL che può farlo. La tua migliore opzione è esattamente quello che hai proposto, penso. L'implementazione di ValueWrapper non deve essere più complicata di quella che hai proposto.

Ovviamente, non è garantito il thread-safe, ma se è necessario è sufficiente convertire la proprietà automatica in una standard con una variabile di supporto e contrassegnare il campo come volatile (per assicurarsi che il valore sia aggiornato- fino ad oggi in ogni momento).

+2

La creazione di un campo volatile non è sufficiente per garantire la sicurezza dei thread, in quanto la scrittura di tipi di valori arbitrari non è garantita dalla specifica C#. Volatile non garantisce l'atomicità, elimina solo alcuni problemi di ordinamento indotti dall'ottimizzazione del compilatore. –

+0

Se ti interessa la sicurezza del filo, usa le serrature. –

+0

@Eric: Sì, buon punto. Originariamente ho scritto che garantisce l'atomicità, ma poi l'ho rimosso rapidamente quando ho capito che non erano necessariamente i casi. – Noldorin

5

Se avete solo bisogno di scrivere il valore poi un'altra tecnica sarebbe:

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

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

Per coincidenza, farò una serie sulle ragioni per cui ci sono così tante restrizioni goofy sui blocchi iteratore nel mio blog nel mese di luglio. "Perché non ci sono parametri di riferimento?" sarà all'inizio della serie.

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

0

Ho sempre pensato che la BCL in realtà dovrebbe avere una classe e l'interfaccia simile al seguente:

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

Anche se molte persone istintivamente avvolgono i campi in auto-proprietà, campi spesso consentire più pulito e codice più efficiente, specialmente quando si usano i tipi di valore. In molte situazioni, l'aumento dell'incapsulamento che si può ottenere usando proprietà può valere il costo in termini di efficienza e semantica, ma quando l'intero scopo di un tipo deve essere un oggetto di classe il cui stato è completamente esposto e mutabile, tale incapsulamento è controproducente.

L'interfaccia è incluso non perché molti utenti di un MutableWrapper<T> vorrebbe utilizzare l'interfaccia, invece, ma piuttosto perché un IReadWriteActUpon<T> potrebbe essere utile in una varietà di situazioni, alcune delle quali comportino l'incapsulamento, e qualcuno che ha un'istanza di MutableWrapper<T> potrebbe desiderare di passarlo al codice che è progettato per funzionare con i dati incapsulati in un'interfaccia IReadWriteActUpon<T>.

Problemi correlati