2010-01-07 8 views
6

Estoy escribiendo un método en C# (2.0) que tiene que devolver una colección de objetos simples. Normalmente haría algo como esto:Declarar que se devuelve una estructura desde un método de interfaz

class MyWidget 
{ 
    struct LittleThing 
    { 
     int foo; 
     DateTime bar; 
    } 

    public IList<LittleThing> LookupThings() 
    { 
     // etc. 
    } 
} 

Sin embargo, tengo que declarar este método en una interfaz. La persona que llama no puede ver MyWidget, solo una interfaz IWidget. La configuración anterior no funciona en esa situación, porque C# no permite definir tipos dentro de una interfaz. ¿Cuál es la mejor forma o la mejor manera de hacer tal declaración?

Lo más directo que pensé es simplemente declarar LittleThing fuera de la interfaz. Eso no parece genial, por un par de razones. Uno: solo es usado por ese único método en esa clase única, por lo que no parece que LittleThing deba ser un tipo independiente que simplemente flota por sí mismo. Dos: si se escriben métodos similares para otras clases, devolverán diferentes tipos de datos (por buenas razones de diseño), y no quiero saturar el espacio de nombres con una tonelada de estructuras con nombres similares que difieren solo ligeramente. de cada uno.

Si pudiéramos actualizar nuestra versión de .Net, simplemente devolvería un Tuple<>, pero eso no va a ser una opción por algún tiempo.

[Editado para agregar: El pequeño objeto no necesita contener más de dos campos, por lo que se KeyValuePair<K,V> no es cortarlo.]

[Editado para añadir más: IWidget se implementa por una sola clase, Widget . Creo que es extraño tener una interfaz para una sola clase, pero esto se hizo para satisfacer una antigua política de codificación que requería que el contrato siempre estuviera en un ensamblaje separado de la implementación. Dicha política se ha ido, pero no tenemos los recursos para refactorizar toda la aplicación y eliminar todas las interfaces innecesarias.]

¿Cuál es la mejor práctica?

+0

¿Cómo le gustaría llamar a la Método LookupThings? El código de llamada debe tener alguna forma de saber qué se devuelve. – Wilhelm

+0

Wilhelm, ese es el quid de mi pregunta. Sé que necesito declarar el tipo del valor de retorno * en algún lugar *. Lo que estoy preguntando es el lugar más sensato para poner esa declaración. – Auraseer

+0

Si LookupThings es el único método que devuelve el tipo, y espera que otras clases usen otros tipos, ¿por qué es parte del contrato? Parece que las clases de implementación pueden hacer cualquier cosa con este método en particular. – Wilhelm

Respuesta

1

Si nos podían cambiar nuestra versión de .Net, me acaba de devolver una tupla <>, pero eso no va a ser una opción para algún tiempo.

¿Por qué esperar? No es como una tupla es algo complicado.Aquí está el código para una 3-tupla.

public struct Tuple<TItem1, TItem2, TItem3> 
{ 
    public Tuple(TItem1 item1, TItem2 item2, TItem3 item3) 
    { 
     this = new Tuple<TItem1, TItem2, TItem3>(); 
     Item1 = item1; 
     Item2 = item2; 
     Item3 = item3; 
    } 

    public static bool operator !=(Tuple<TItem1, TItem2, TItem3> left, Tuple<TItem1, TItem2, TItem3> right) 
    { return left.Equals(right); } 

    public static bool operator ==(Tuple<TItem1, TItem2, TItem3> left, Tuple<TItem1, TItem2, TItem3> right) 
    { return !left.Equals(right); } 

    public TItem1 Item1 { get; private set; } 
    public TItem2 Item2 { get; private set; } 
    public TItem3 Item3 { get; private set; } 

    public override bool Equals(object obj) 
    { 
     if (obj is Tuple<TItem1, TItem2, TItem3>) 
     { 
      var other = (Tuple<TItem1, TItem2, TItem3>)obj; 
      return Object.Equals(Item1, other.Item1) 
       && Object.Equals(Item2, other.Item2) 
       && Object.Equals(Item3, other.Item3); 
     } 
     return false; 
    } 

    public override int GetHashCode() 
    { 
     return ((this.Item1 != null) ? this.Item1.GetHashCode() : 0) 
      ^((this.Item2 != null) ? this.Item2.GetHashCode() : 0) 
      ^((this.Item3 != null) ? this.Item3.GetHashCode() : 0); 
    } 
} 

Como puede ver, no es gran cosa. Lo que he hecho en mi proyecto actual es implementar 2, 3 y 4 tuplas, junto con una clase estática Tuple con los métodos Create, que reflejan exactamente los tipos de tupla de .NET 4. Si está realmente paranoico, puede usar el reflector para ver el código fuente desensamblado para la tupla .NET 4 y copiarlo literalmente

Cuando finalmente actualicemos a .NET 4, simplemente eliminaremos las clases, o #ifdef fuera de

+0

Se ha decidido que no creo nuevos contenedores genéricos para transmitir información. Si realmente necesito devolver más de un valor, voy a crear un tipo con un tipo de letra significativo y nombres de campo significativos. (No me preguntes por qué Tuple de repente estará bien tan pronto como sea un tipo .net definido. Ese es un argumento que no gané). – Auraseer

+0

lol. Parece que estás trabajando en un lugar bastante atrasado ... ¿Puedes utilizar Func o cualquiera de ese tipo? –

+0

Los genéricos están bien para las clases y los métodos; por ejemplo, podría tener una CustomWeirdSortedCollection . La política de codificación solo dice que no puedo crear contenedores genéricos solo de datos. Mi suposición es que el escritor de políticas estaba traumatizado por alguna mala experiencia anterior, con un código que usaba los contenedores anónimos y los tipos anónimos donde debería haber tenido clases. Quiero decir que "hacia atrás" es una descripción demasiado dura para este lugar, por lo que es mejor no admitir que solo se actualizaron a .Net 2 en octubre pasado. – Auraseer

2
  1. ¿Por qué utilizar las estructuras? ¿Por qué no usar clases en su lugar?
  2. Declare la clase por separado, como una clase pública.
+0

Para mis propósitos, la única diferencia notable es que las estructuras son un poco más pequeñas. Como los datos que contiene el objeto son pequeños y mi lista es potencialmente muy grande, el cambio tiene un efecto notable en el uso de la memoria. Si necesito convertirlo en una clase más tarde, solo necesito cambiar la palabra clave. – Auraseer

+0

Tiene razón sobre el uso de memoria, pero recuerde que las estructuras son tipos de valores, no tipos de referencia. var s2 = othertruct siempre hace una * copia * de todos los campos en la estructura, ¡no una referencia a la misma estructura! –

+0

Muy cierto. Es por eso que dije "para mis propósitos". En el uso esperado, la persona que llama no va a copiar estos objetos; él procesará o mostrará la lista completa. Si fuera razonable, por ejemplo, filtrar subconjuntos grandes y crear nuevas listas de ellos, entonces me gustaría usar una clase en su lugar. – Auraseer

6

Si el "LittleThing" solo tiene dos valores, puede devolver KeyValuePair<TKey,TValue>.

Si hay más de dos, siempre se puede hacer su propia clase de tupla, y reemplazarlo con .NET de 4 cuando finalmente se muda a .NET 4.

De lo contrario, sólo definiría la estructura con la interfaz, e incluirlo como parte de su API. Los espacios de nombres se ocupan de la cuestión de nombrar ...

1

Puede declarar el struct fuera del interface, pero dentro de un namespace anidado.

+0

Esto no funcionará si el espacio de nombres es el mismo que el nombre de la interfaz, que es más o menos lo que está tratando de lograr. – Joe

+0

Quizás ponga la interfaz y la estructura, ambas, en el espacio de nombres anidado: y luego cualquier clase que quiera usar las dos (use la interfaz y su estructura asociada) puede hacer un 'using' en ese espacio de nombres. – ChrisW

0

Ignorando el significado del nombre de la estructura aquí, puede usar la versión genérica de KeyValuePair.

0

Las interfaces solo definen qué se puede implementar en otra clase, por lo que no se puede dar nada dentro de una definición. Incluyendo clases y estructuras.

Dicho esto, un patrón usado a menudo para evitar esta restricción es definir una clase con el mismo nombre (excluyendo el 'I' prefijo) para proporcionar las definiciones relacionadas, tales como:

public interface IWidget 
{ 
    IList<Widget.LittleThing> LookupThings(); 
} 

// For definitions used by IWidget 
public class Widget 
{ 
    public struct LittleThing 
    { 
     int foo; 
     DateTime bar; 
    } 
} 

Ejemplos de este patrón se puede encontrar en el BCL, particularmente con los métodos genéricos y de extensión, pero también con los valores predeterminados (por ejemplo, EqualityComparer<T>.Default) e incluso las implementaciones predeterminadas (por ej., IList<T> y List<T>). Lo anterior es solo otro caso.

+0

No estoy seguro de entender lo que quieres decir aquí. ¿No es esto solo una referencia circular? La clase My Widget implementa IWidget, por lo que la interfaz no puede depender de su implementador. Además, como mi interlocutor solo ve la interfaz, no podría resolver Widget.anything. – Auraseer

+0

Quiero decir que la clase de Widget (que debería haber hecho una clase estática) debería definirse en su biblioteca con IWidget. Es el nombre que une la interfaz con la clase. Esta clase no implementa la interfaz, sino que proporciona definiciones relacionadas, ya que las interfaces no pueden definir nada. Similar a lo que hace la clase C# EqualityComparer para IEqualityComparer . Asumo que su clase de implementación se llama "MyWidget", ya que llamarlo "Widget" sería confuso. – Joe

+0

Ah, lo entiendo ahora. ¿Su clase de Widget aquí no contiene ninguna funcionalidad, y solo existe para que otros tipos sean anidados dentro? Eso parece equivalente a hacer un espacio de nombres. IMO sería preferible un espacio de nombre real, porque se lo ve con más frecuencia y se puede usar con la directiva de uso. – Auraseer

0

Por lo tanto, la lectura de todo lo que han comentado aquí es lo que pienso:

  1. Si todos los tipos de aplicación iwidget retorno LittleThing:

    Entonces me consideran las mejores prácticas a LittleThing estar al mismo nivel de espacio de nombres de IWidget, preferiblemente en un espacio de nombre de Widgets. Sin embargo, realmente debería considerar hacer de LittleThing una clase. Según su descripción del problema, parece que no puede ser tan poco, ya que cada vez que la clase implementa IWidget usa algunos campos pero no otros.

  2. Si todos los tipos de aplicación iwidget necesitan para devolver valores ligeramente diferentes, pero con un comportamiento similar:

    considere hacer una interfaz iThing. Entonces cada aplicación iThing sería totalmente dependiente de la clase que lo devuelve, por lo que entonces se puede declarar la estructura o clase que implementa iThing dentro de la clase que implementa iwidget así:

    interface IWidget 
    { 
        IList<IThing> LookupThings() 
        { 
         … 
        } 
    } 
    
    interface IThing 
    { 
        … 
    } 
    
    class MyWidget : IWidget 
    { 
        IList<IThing> IWidget.LookupThings() 
        { 
         … 
        } 
    
        private class MyWidgetThings : IThing 
        { 
         … 
        } 
    } 
    
Cuestiones relacionadas