2010-07-27 14 views
24

En C# encuentro indexed properties extremadamente útil. Por ejemplo:Fácil creación de propiedades que admiten indexación en C#

var myObj = new MyClass(); 
myObj[42] = "hello"; 
Console.WriteLine(myObj[42]); 

Sin embargo por lo que yo sé no hay azúcar sintáctica para apoyar a sí mismos campos que soporte de indexación (por favor, corríjanme si me equivoco). Por ejemplo:

var myObj = new MyClass(); 
myObj.field[42] = "hello"; 
Console.WriteLine(myObj.field[42]); 

La razón que necesito esto es que ya estoy usando la propiedad índice en mi clase, pero tengo GetNumX(), GetX() y SetX() funciones de la siguiente manera:

public int NumTargetSlots { 
    get { return _Maker.NumRefs; } 
} 
public ReferenceTarget GetTarget(int n) { 
    return ReferenceTarget.Create(_Maker.GetReference(n)); 
} 
public void SetTarget(int n, ReferenceTarget rt) { 
    _Maker.ReplaceReference(n, rt._Target, true); 
} 

Como probablemente pueda ver la exposición de estos como una propiedad de campo indexable tendría más sentido. Podría escribir una clase personalizada para lograr esto cada vez que quiera el azúcar sintáctico, pero todo el código repetitivo simplemente parece innecesario.

Así que escribí una clase personalizada para encapsular el texto estándar y para facilitar la creación de propiedades que puedan indexarse. De esta manera puedo añadir una nueva propiedad de la siguiente manera:

public IndexedProperty<ReferenceTarget> TargetArray { 
    get { 
     return new IndexedProperty<int, ReferenceTarget>(
      (int n) => GetTarget(n), 
      (int n, ReferenceTarget rt) => SetTarget(n, rt)); 
     } 
} 

El código para esta nueva clase IndexedProperty parece:

public class IndexedProperty<IndexT, ValueT> 
{ 
    Action<IndexT, ValueT> setAction; 
    Func<IndexT, ValueT> getFunc; 

    public IndexedProperty(Func<IndexT, ValueT> getFunc, Action<IndexT, ValueT> setAction) 
    { 
     this.getFunc = getFunc; 
     this.setAction = setAction; 
    } 

    public ValueT this[IndexT i] 
    { 
     get { 
      return getFunc(i); 
     } 
     set { 
      setAction(i, value); 
     } 
    } 
} 

Así que mi pregunta es: hay una mejor manera de hacer todas esto?

Bueno, para ser específico, ¿existe una forma más idiomática en C# para crear una propiedad de campo indexable y, de no ser así, cómo podría mejorar mi clase IndexedProperty?

EDIT: Después de más investigaciones, Jon Skeet llama a esto un "named indexer".

+0

Compruebe http://stackoverflow.com/questions/23081402/named-indexed-property-in-c/23081403#23081403 – Spook

Respuesta

6

Bueno, lo más simple es hacer que la propiedad devuelva un objeto que implementa IList.

Recuerda que solo porque implementa IList no significa que sea una colección en sí misma, sino que implementa ciertos métodos.

+0

Hmmmm ... Me gusta esta línea de pensamiento. Sin embargo, el problema que tengo con IList son los miembros como "Agregar", "Eliminar", etc. Sería deshonesto exponer la propiedad como un IList simplemente ignóralos. Tal vez hay otra interfaz que tendría más sentido? – cdiggins

+3

Puede tener un 'IList' de solo lectura. 'Array' y' ReadOnlyCollection 'implementan' IList'; Ambas clases generan excepciones cuando se invoca 'Agregar', 'Eliminar', etc. –

+0

Pero no es un IList de "solo lectura". Puede establecer elementos en índices particulares. Claramente no es una lista, pero tampoco lo es Array. Sin embargo, ¡no sabía que "Array" implementó "IList"! Eso parece obviamente una mala decisión de diseño. Por supuesto, ahora se clasificaría como "idiomático C#". Estoy en la valla. – cdiggins

3

¿Por qué su clase no hereda IList? Entonces puede usar el índice y agregarle sus propias propiedades. Aunque todavía tendrá las funciones Agregar y Eliminar, no es deshonesto no usarlas. Además, puede ser útil tenerlos en el futuro.

Para obtener más información sobre listas y matrices Salida: Which is better to use array or List<>?

EDIT:

MSDN tiene un artículo sobre propiedades del índice es posible que desee echar un vistazo a. No parece complicado solo tedioso.

http://msdn.microsoft.com/en-us/library/aa288464(VS.71).aspx

hay otra opción donde se puede crear una alternativa Agregar método, pero dependiendo del tipo de objeto que su método add no siempre se llama.Se explica aquí:

How do I override List<T>'s Add method in C#?

EDIT 2: Al igual que en el primer post

¿Por qué no tienes un objeto de lista oculta en su clase y luego simplemente crear sus propios métodos para obtener los datos . De esta forma, no se ven Agregar y Eliminar y la lista ya está indexada.

También lo que quiere decir con "indexador nombrado" es que busca el equivalente de la fila ["My_Column_Name"]. Hay un artículo de MSDN que encontré que puede ser útil, ya que parece mostrar la forma básica de implementar esa propiedad.

http://msdn.microsoft.com/en-us/library/146h6tk5.aspx

class Test 
    { 
     private List<T> index; 
     public T this[string name]{ get; set; } 
     public T this[int i] 
     { 
      get 
      { 
       return index[i]; 
      } 
      set 
      { 
       index[i] = value; 
      } 
     } 
    } 
+0

Quiero que mis tipos indiquen el uso previsto. El problema con el uso de un IList, es que necesito agregar documentación para los consumidores de mi código, diciendo que sí implemento IList, pero no debe usar "Agregar" o "Eliminar". Prefiero el código de auto-documentación. – cdiggins

+1

@cdiggins Tengo dos opiniones al respecto.Estoy de acuerdo con usted en principio, pero para 'IList' y' ICollection', la documentación del framework señala específicamente que las clases implementadas deberían arrojar 'NotSupportedException' sobre una llamada a' Add', 'Clear',' Insert', 'Remove' , o métodos 'RemoveAt' si la colección es de solo lectura (es decir, si la propiedad' IsReadOnly' devuelve verdadero). Es razonable esperar que sus usuarios usen su código de una manera que se ajuste a la documentación, por lo que creo que usar 'IList' es razonable, y en realidad es la forma recomendada de hacer lo que desee. – Dathan

+0

Hola, Gage, ya me había vinculado a ese artículo de MSDN en mi publicación. – cdiggins

5

Creo que el diseño que has enviado es el camino a seguir, con la única diferencia de que yo definiría una interfaz:

public interface IIndexed<IndexT, ValueT> 
{ 
    ValueT this[IndexT i] { get; set; } 
} 

Y para los casos comunes, lo usaría la clase que pones en la pregunta original (que implementaría esta interfaz).

Sería bueno si la biblioteca de la clase base nos proporcionara una interfaz adecuada, pero no es así. Devolver un IList aquí sería una perversión.

4

Esto no responde a su pregunta, pero es interesante observar que CIL admite la realización de propiedades como las que ha descrito; algunos idiomas (por ejemplo, F #) le permitirán definirlos de esa manera también.

El indexador this[] en C# es solo una instancia específica de uno de estos que se renombró a Item cuando construyes tu aplicación. El compilador de C# solo sabe cómo leer este, por lo que si escribe un "indexador nombrado" llamado Target en una biblioteca F # e intenta usarlo en un C#, la única forma de acceder a la propiedad es a través del ... get_Target(int) y void set_Target(int, ...) métodos. Sucks.

7

Esta no es la forma correcta de usar StackOverflow, pero encontré su idea tan útil que la extendí. Pensé que ahorraría tiempo a las personas si publicara lo que se me ocurrió y un ejemplo de cómo usarlo.

En primer lugar, tenía que ser capaz de soportar las propiedades get-solamente y configuración únicos, así que hice una ligera variación de su código para estos escenarios:

obtener y establecer (muy pequeños cambios):

public class IndexedProperty<TIndex, TValue> 
{ 
    readonly Action<TIndex, TValue> SetAction; 
    readonly Func<TIndex, TValue> GetFunc; 

    public IndexedProperty(Func<TIndex, TValue> getFunc, Action<TIndex, TValue> setAction) 
    { 
     this.GetFunc = getFunc; 
     this.SetAction = setAction; 
    } 

    public TValue this[TIndex i] 
    { 
     get 
     { 
      return GetFunc(i); 
     } 
     set 
     { 
      SetAction(i, value); 
     } 
    } 
} 

Obtener Sólo:

public class ReadOnlyIndexedProperty<TIndex, TValue> 
{ 
    readonly Func<TIndex, TValue> GetFunc; 

    public ReadOnlyIndexedProperty(Func<TIndex, TValue> getFunc) 
    { 
     this.GetFunc = getFunc; 
    } 

    public TValue this[TIndex i] 
    { 
     get 
     { 
      return GetFunc(i); 
     } 
    } 
} 

Conjunto Sólo:

public class WriteOnlyIndexedProperty<TIndex, TValue> 
{ 
    readonly Action<TIndex, TValue> SetAction; 

    public WriteOnlyIndexedProperty(Action<TIndex, TValue> setAction) 
    { 
     this.SetAction = setAction; 
    } 

    public TValue this[TIndex i] 
    { 
     set 
     { 
      SetAction(i, value); 
     } 
    } 
} 

Ejemplo

He aquí un ejemplo de uso simple. Yo heredo de Collection y creo un indexador nombrado, como lo llamó Jon Skeet.Este ejemplo está destinado a ser simple, no es práctico:

public class ExampleCollection<T> : Collection<T> 
{ 
    public IndexedProperty<int, T> ExampleProperty 
    { 
     get 
     { 
      return new IndexedProperty<int, T>(GetIndex, SetIndex); 
     } 
    } 

    private T GetIndex(int index) 
    { 
     return this[index]; 
    } 
    private void SetIndex(int index, T value) 
    { 
     this[index] = value; 
    } 
} 

ExampleCollection en el salvaje

Esta unidad de prueba construido a toda prisa muestra cómo se ve cuando ExampleCollection en un proyecto:

[TestClass] 
public class IndexPropertyTests 
{ 
    [TestMethod] 
    public void IndexPropertyTest() 
    { 
     var MyExample = new ExampleCollection<string>(); 
     MyExample.Add("a"); 
     MyExample.Add("b"); 

     Assert.IsTrue(MyExample.ExampleProperty[0] == "a"); 
     Assert.IsTrue(MyExample.ExampleProperty[1] == "b"); 

     MyExample.ExampleProperty[0] = "c"; 

     Assert.IsTrue(MyExample.ExampleProperty[0] == "c"); 

    } 
} 

Por último, si desea usar las versiones solo para y de solo ajuste, que se ve así:

public ReadOnlyIndexedProperty<int, T> ExampleProperty 
    { 
     get 
     { 
      return new ReadOnlyIndexedProperty<int, T>(GetIndex); 
     } 
    } 

O:

public WriteOnlyIndexedProperty<int, T> ExampleProperty 
    { 
     get 
     { 
      return new WriteOnlyIndexedProperty<int, T>(SetIndex); 
     } 
    } 

En ambos casos, el resultado funciona exactamente de la manera que se puede esperar un encuentro solamente/set establecimiento sólo para comportarse.

+1

malo está presionando innecesariamente el GC con la repetida 'nueva propiedad indexada' en el captador de propiedades. Además, el uso de delegados probablemente impida un montón de optimizaciones. Ambos pueden ser aceptables en ciertos casos de uso, pero en general no recomendaría un código como ese. Escribir clases de indexadores especializados seguramente es más trabajo, pero funcionará mucho mejor. –

+0

Su ejemplo es confuso porque tiene acceso a una colección, cuyos elementos ya se podía acceder sin la clase de indexador personalizado. Es mejor usar una 'clase CExample {cadena privada [] m_Texts; cadena privada GetText (int index) {return m_Texts [index];} 'para demostrar el uso común. –

2

Después de algunas investigaciones, se me ocurrió una solución ligeramente diferente que se ajustaba mejor a mis necesidades. El ejemplo es un poco inventado, pero se adapta a lo que necesito para adaptarlo.

Uso:

MyClass MC = new MyClass(); 
int x = MC.IntProperty[5]; 

Y el código para hacer que funcione:

public class MyClass 
{ 
    public readonly IntIndexing IntProperty; 

    public MyClass() 
    { 
     IntProperty = new IntIndexing(this); 
    } 

    private int GetInt(int index) 
    { 
     switch (index) 
     { 
      case 1: 
       return 56; 
      case 2: 
       return 47; 
      case 3: 
       return 88; 
      case 4: 
       return 12; 
      case 5: 
       return 32; 
      default: 
       return -1; 
     } 
    } 

    public class IntIndexing 
    { 
     private MyClass MC; 

     internal IntIndexing(MyClass mc) 
     { 
      MC = mc; 
     } 

     public int this[int index] 
     { 
      get { return MC.GetInt(index); } 
     } 
    } 
} 
+0

Esto es lo que estaba buscando. Compacto. – MrHIDEn

0

Trate interfaces implementadas de manera explícita, como se muestra en la segunda forma propuesta en tu respuesta aquí: Named indexed property in C#?

Cuestiones relacionadas