una referencia puede propiedad por pase pasar parámetro parametros indizador como c# parameters ref out

c# - referencia - una propiedad o un indizador no se puede pasar como parámetro out o ref



¿Por qué los métodos de iterador no pueden tomar parámetros ''ref'' o ''out''? (5)

Intenté esto antes hoy:

public interface IFoo { IEnumerable<int> GetItems_A( ref int somethingElse ); IEnumerable<int> GetItems_B( ref int somethingElse ); } public class Bar : IFoo { public IEnumerable<int> GetItems_A( ref int somethingElse ) { // Ok... } public IEnumerable<int> GetItems_B( ref int somethingElse ) { yield return 7; // CS1623: Iterators cannot have ref or out parameters } }

¿Cuál es la razón detrás de esto?


En un nivel alto, una variable de referencia puede apuntar a muchas ubicaciones, incluidos los tipos de valor que se encuentran en la pila. El momento en el que se crea inicialmente el iterador llamando al método del iterador y cuando se asignaría la variable ref son dos momentos muy diferentes. No es posible garantizar que la variable que originalmente se pasó por referencia todavía esté presente cuando el iterador se ejecute realmente. Por lo tanto no está permitido (o verificable)


He solucionado este problema utilizando funciones, cuando el valor que necesito devolver se deriva de los elementos iterados:

// One of the problems with Enumerable.Count() is // that it is a ''terminator'', meaning that it will // execute the expression it is given, and discard // the resulting sequence. To count the number of // items in a sequence without discarding it, we // can use this variant that takes an Action<int> // (or Action<long>), invokes it and passes it the // number of items that were yielded. // // Example: This example allows us to find out // how many items were in the original // source sequence ''items'', as well as // the number of items consumed by the // call to Sum(), without causing any // LINQ expressions involved to execute // multiple times. // // int start = 0; // the number of items from the original source // int finished = 0; // the number of items in the resulting sequence // // IEnumerable<KeyValuePair<string, double>> items = // assumed to be an iterator // // var result = items.Count( i => start = i ) // .Where( p => p.Key = "Banana" ) // .Select( p => p.Value ) // .Count( i => finished = i ) // .Sum(); // // // by getting the count of items operated // // on by Sum(), we can calculate an average: // // double average = result / (double) finished; // // Console.WriteLine( "started with {0} items", start ); // Console.WriteLine( "finished with {0} items", finished ); // public static IEnumerable<T> Count<T>( this IEnumerable<T> source, Action<int> receiver ) { int i = 0; foreach( T item in source ) { yield return item; ++i ; } receiver( i ); } public static IEnumerable<T> Count<T>( this IEnumerable<T> source, Action<long> receiver ) { long i = 0; foreach( T item in source ) { yield return item; ++i ; } receiver( i ); }


Los iteradores C # son máquinas estatales internamente. Cada vez que yield return algo, el lugar donde lo dejó debe guardarse junto con el estado de las variables locales para que pueda regresar y continuar desde allí.

Para mantener este estado, el compilador de C # crea una clase para contener variables locales y el lugar desde donde debe continuar. No es posible tener un valor de ref o de out como un campo en una clase. En consecuencia, si se le permitiera declarar un parámetro como ref o out , no habría manera de mantener la instantánea completa de la función en el momento en que la habíamos dejado.

EDITAR: Técnicamente, no todos los métodos que devuelven IEnumerable<T> se consideran iteradores. Solo aquellos que usan el yield para producir una secuencia directamente se consideran iteradores. Por lo tanto, aunque dividir el iterador en dos métodos es una solución agradable y común, no contradice lo que acabo de decir. El método externo (que no usa el yield directamente) no se considera un iterador.


Otros han explicado por qué su iterador no puede tener un parámetro ref. Aquí hay una alternativa simple:

public interface IFoo { IEnumerable<int> GetItems( int[] box ); ... } public class Bar : IFoo { public IEnumerable<int> GetItems( int[] box ) { int value = box[0]; // use and change value and yield to your heart''s content box[0] = value; } }

Si tiene varios elementos para pasar y pasar, defina una clase para retenerlos.


Si desea devolver tanto un iterador como un int desde su método, una solución es la siguiente:

public class Bar : IFoo { public IEnumerable<int> GetItems( ref int somethingElse ) { somethingElse = 42; return GetItemsCore(); } private IEnumerable<int> GetItemsCore(); { yield return 7; } }

Debe tener en cuenta que no se ejecuta ninguno de los códigos dentro de un método de iterador (es decir, básicamente un método que contiene yield return o yield break ) hasta que se llame al método MoveNext() en el Enumerador. Entonces, si pudieras usar o ref tu método de iterador, obtendrías un comportamiento sorprendente como este:

// This will not compile: public IEnumerable<int> GetItems( ref int somethingElse ) { somethingElse = 42; yield return 7; } // ... int somethingElse = 0; IEnumerable<int> items = GetItems( ref somethingElse ); // at this point somethingElse would still be 0 items.GetEnumerator().MoveNext(); // but now the assignment would be executed and somethingElse would be 42

Este es un error común, un problema relacionado es este:

public IEnumerable<int> GetItems( object mayNotBeNull ){ if( mayNotBeNull == null ) throw new NullPointerException(); yield return 7; } // ... IEnumerable<int> items = GetItems( null ); // <- This does not throw items.GetEnumerators().MoveNext(); // <- But this does

Por lo tanto, un buen patrón es separar los métodos de iterador en dos partes: una para ejecutar inmediatamente y otra que contiene el código que debe ejecutarse de forma perezosa.

public IEnumerable<int> GetItems( object mayNotBeNull ){ if( mayNotBeNull == null ) throw new NullPointerException(); // other quick checks return GetItemsCore( mayNotBeNull ); } private IEnumerable<int> GetItemsCore( object mayNotBeNull ){ SlowRunningMethod(); CallToDatabase(); // etc yield return 7; } // ... IEnumerable<int> items = GetItems( null ); // <- Now this will throw

EDITAR: Si realmente desea el comportamiento donde mover el iterador modificaría el parámetro de ref , podría hacer algo como esto:

public static IEnumerable<int> GetItems( Action<int> setter, Func<int> getter ) { setter(42); yield return 7; } //... int local = 0; IEnumerable<int> items = GetItems((x)=>{local = x;}, ()=>local); Console.WriteLine(local); // 0 items.GetEnumerator().MoveNext(); Console.WriteLine(local); // 42