Hay varias cosas que suceden en esta pregunta ...
Es posible que una estructura implemente una interfaz, pero existen preocupaciones que surgen con la conversión, la mutabilidad y el rendimiento. Consulte esta publicación para obtener más detalles: https://docs.microsoft.com/en-us/archive/blogs/abhinaba/c-structs-and-interface
En general, las estructuras deben usarse para objetos que tienen semántica de tipo valor. Al implementar una interfaz en una estructura, puede encontrarse con problemas de boxeo a medida que la estructura va y viene entre la estructura y la interfaz. Como resultado del encajonamiento, las operaciones que cambian el estado interno de la estructura pueden no comportarse correctamente.
IComparable<T>
yIEquatable<T>
. Almacenar una estructuraFoo
en una variable de tipoIComparable<Foo>
requeriría encuadrar, pero si un tipo genéricoT
está restringido aIComparable<T>
uno, puede compararlo con otroT
sin tener que enmarcar ninguno de ellos, y sin tener que saber nadaT
más que implementa la restricción. Este comportamiento ventajoso solo es posible gracias a la capacidad de las estructuras para implementar interfaces. Habiendo dicho eso ...Dado que nadie más proporcionó explícitamente esta respuesta, agregaré lo siguiente:
La implementación de una interfaz en una estructura no tiene ninguna consecuencia negativa.
Cualquier variable del tipo de interfaz utilizada para contener una estructura dará como resultado un valor en caja de esa estructura que se utiliza. Si la estructura es inmutable (algo bueno), esto es, en el peor de los casos, un problema de rendimiento a menos que:
Ambos serían poco probables, en cambio, es probable que esté haciendo una de las siguientes cosas:
Genéricos
Quizás muchas razones razonables para que las estructuras implementen interfaces es que pueden usarse dentro de un contexto genérico con restricciones . Cuando se usa de esta manera, la variable así:
class Foo<T> : IEquatable<Foo<T>> where T : IEquatable<T> { private readonly T a; public bool Equals(Foo<T> other) { return this.a.Equals(other.a); } }
new()
oclass
.Entonces this.a NO es una referencia de interfaz, por lo tanto, no causa un cuadro de lo que sea que se coloque en él. Además, cuando el compilador de c # compila las clases genéricas y necesita insertar invocaciones de los métodos de instancia definidos en instancias del parámetro Tipo T, puede usar el código de operación restringido :
Esto evita el boxing y dado que el tipo de valor está implementando la interfaz, se debe implementar el método, por lo que no ocurrirá boxing. En el ejemplo anterior, la
Equals()
invocación se realiza sin ningún recuadro sobre esto . A 1 .API de baja fricción
La mayoría de las estructuras deben tener una semántica de tipo primitivo donde los valores idénticos bit a bit se consideran iguales 2 . El tiempo de ejecución proporcionará dicho comportamiento en el implícito,
Equals()
pero esto puede ser lento. Además, esta igualdad implícita no se expone como una implementación deIEquatable<T>
y, por lo tanto, evita que las estructuras se utilicen fácilmente como claves para los diccionarios, a menos que lo implementen explícitamente ellos mismos. Por lo tanto, es común que muchos tipos de estructuras públicas declaren que implementanIEquatable<T>
(dóndeT
están ellos mismos) para hacer esto más fácil y con un mejor rendimiento, así como coherente con el comportamiento de muchos tipos de valores existentes dentro de CLR BCL.Todas las primitivas en el BCL implementan como mínimo:
IComparable
IConvertible
IComparable<T>
IEquatable<T>
(Y asíIEquatable
)Muchos también implementan
IFormattable
, además, muchos de los tipos de valores definidos por el sistema como DateTime, TimeSpan y Guid implementan muchos o todos estos también. Si está implementando un tipo similarmente 'ampliamente útil' como una estructura numérica compleja o algunos valores textuales de ancho fijo, entonces implementar muchas de estas interfaces comunes (correctamente) hará que su estructura sea más útil y utilizable.Exclusiones
Obviamente, si la interfaz implica fuertemente mutabilidad (por ejemplo
ICollection
), implementarla es una mala idea, ya que significaría que hizo que la estructura fuera mutable (lo que lleva a los tipos de errores descritos ya en los que las modificaciones ocurren en el valor en caja en lugar del original ) o confunde a los usuarios al ignorar las implicaciones de los métodos comoAdd()
o al lanzar excepciones.Muchas interfaces NO implican mutabilidad (como
IFormattable
) y sirven como la forma idiomática de exponer cierta funcionalidad de manera consistente. A menudo, al usuario de la estructura no le importará ninguna sobrecarga de boxeo por tal comportamiento.Resumen
Cuando se hace con sensatez, en tipos de valores inmutables, la implementación de interfaces útiles es una buena idea
Notas:
1: Tenga en cuenta que el compilador puede usar esto al invocar métodos virtuales en variables que se sabe que son de un tipo de estructura específico pero en las que se requiere invocar un método virtual. Por ejemplo:
List<int> l = new List<int>(); foreach(var x in l) ;//no-op
El enumerador devuelto por la Lista es una estructura, una optimización para evitar una asignación al enumerar la lista (con algunas consecuencias interesantes ). Sin embargo la semántica de foreach especifican que si los implementos empadronador
IDisposable
a continuaciónDispose()
se llamará una vez completada la iteración. Obviamente, que esto ocurra a través de una llamada en caja eliminaría cualquier beneficio de que el enumerador sea una estructura (de hecho, sería peor). Peor aún, si dispose call modifica el estado del enumerador de alguna manera, esto sucedería en la instancia en caja y podrían introducirse muchos errores sutiles en casos complejos. Por tanto, el IL emitido en este tipo de situaciones es:Por lo tanto, la implementación de IDisposable no causa ningún problema de rendimiento y el aspecto mutable (lamentable) del enumerador se conserva si el método Dispose realmente hace algo.
2: double y float son excepciones a esta regla donde los valores de NaN no se consideran iguales.
fuente
struct
en uninterface
.En algunos casos, puede ser bueno que una estructura implemente una interfaz (si nunca fue útil, es dudoso que los creadores de .net la hayan proporcionado). Si una estructura implementa una interfaz de solo lectura como
IEquatable<T>
, almacenar la estructura en una ubicación de almacenamiento (variable, parámetro, elemento de matriz, etc.) de tipoIEquatable<T>
requerirá que esté en caja (cada tipo de estructura en realidad define dos tipos de cosas: un almacenamiento tipo de ubicación que se comporta como un tipo de valor y un tipo de objeto de montón que se comporta como un tipo de clase; el primero es implícitamente convertible al segundo - "boxing" - y el segundo puede convertirse al primero a través de una conversión explícita-- "unboxing"). Sin embargo, es posible explotar la implementación de una estructura de una interfaz sin encajonar, utilizando lo que se denominan genéricos restringidos.Por ejemplo, si uno tuviera un método
CompareTwoThings<T>(T thing1, T thing2) where T:IComparable<T>
, dicho método podría llamarthing1.Compare(thing2)
sin tener que boxthing1
othing2
. Sithing1
resulta ser, por ejemplo, anInt32
, el tiempo de ejecución lo sabrá cuando genere el código paraCompareTwoThings<Int32>(Int32 thing1, Int32 thing2)
. Dado que sabrá el tipo exacto tanto de la cosa que aloja el método como de la cosa que se pasa como parámetro, no tendrá que marcar ninguno de ellos.El mayor problema con las estructuras que implementan interfaces es que una estructura que se almacena en una ubicación del tipo de interfaz
Object
, oValueType
(a diferencia de una ubicación de su propio tipo) se comportará como un objeto de clase. Para las interfaces de solo lectura, esto generalmente no es un problema, pero para una interfaz mutante comoIEnumerator<T>
esta puede producir una semántica extraña.Considere, por ejemplo, el siguiente código:
List<String> myList = [list containing a bunch of strings] var enumerator1 = myList.GetEnumerator(); // Struct of type List<String>.IEnumerator enumerator1.MoveNext(); // 1 var enumerator2 = enumerator1; enumerator2.MoveNext(); // 2 IEnumerator<string> enumerator3 = enumerator2; enumerator3.MoveNext(); // 3 IEnumerator<string> enumerator4 = enumerator3; enumerator4.MoveNext(); // 4
La declaración marcada # 1 se preparará
enumerator1
para leer el primer elemento. Se copiará el estado de ese enumeradorenumerator2
. La declaración marcada # 2 avanzará esa copia para leer el segundo elemento, pero no afectaráenumerator1
. Luego se copiará el estado de ese segundo enumeradorenumerator3
, que se avanzará mediante la declaración marcada # 3. Entonces, debidoenumerator3
yenumerator4
son los dos tipos de referencia, una referencia alenumerator3
entonces será copiado aenumerator4
, tan marcada comunicado avanzará de manera efectiva tantoenumerator3
yenumerator4
.Algunas personas intentan fingir que los tipos de valores y los tipos de referencia son ambos tipos
Object
, pero eso no es realmente cierto. Los tipos de valor real son convertibles aObject
, pero no son instancias de él. Una instancia de laList<String>.Enumerator
cual se almacena en una ubicación de ese tipo es un tipo de valor y se comporta como un tipo de valor; copiarlo en una ubicación de tipoIEnumerator<String>
lo convertirá en un tipo de referencia y se comportará como un tipo de referencia . El último es una especie deObject
, pero el primero no lo es.Por cierto, un par de notas más: (1) En general, los tipos de clases mutables deberían tener sus
Equals
métodos para probar la igualdad de referencia, pero no hay una forma decente para que una estructura en caja lo haga; (2) a pesar de su nombre,ValueType
es un tipo de clase, no un tipo de valor; todos los tipos derivados deSystem.Enum
son tipos de valor, al igual que todos los tipos que derivan deValueType
con la excepción deSystem.Enum
, pero ambosValueType
ySystem.Enum
son tipos de clase.fuente
Las estructuras se implementan como tipos de valor y las clases son tipos de referencia. Si tiene una variable de tipo Foo, y almacena una instancia de Fubar en ella, la "encajará" en un tipo de referencia, anulando así la ventaja de usar una estructura en primer lugar.
La única razón por la que veo para usar una estructura en lugar de una clase es porque será un tipo de valor y no un tipo de referencia, pero la estructura no puede heredar de una clase. Si tiene la estructura heredando una interfaz y pasa interfaces, pierde esa naturaleza de tipo de valor de la estructura. También podría convertirlo en una clase si necesita interfaces.
fuente
(Bueno, no tengo nada importante que agregar, pero aún no tengo destreza en la edición, así que aquí va ...)
Perfectamente seguro. Nada ilegal con la implementación de interfaces en estructuras. Sin embargo, deberías preguntarte por qué querrías hacerlo.
Sin embargo, la obtención de una referencia de interfaz a una estructura la ENCUENTRA . Entonces, penalización de rendimiento y así sucesivamente.
El único escenario válido en el que puedo pensar en este momento está ilustrado en mi publicación aquí . Cuando desee modificar el estado de una estructura almacenada en una colección, tendrá que hacerlo a través de una interfaz adicional expuesta en la estructura.
fuente
Int32
a un método que acepta un tipo genéricoT:IComparable<Int32>
(que puede ser un parámetro de tipo genérico del método o la clase del método), ese método podrá usar elCompare
método en el objeto pasado sin encasillarlo.Creo que el problema es que causa boxing porque las estructuras son tipos de valor, por lo que hay una pequeña penalización en el rendimiento.
Este enlace sugiere que podría haber otros problemas con él ...
http://blogs.msdn.com/abhinaba/archive/2005/10/05/477238.aspx
fuente
No hay consecuencias para una estructura que implemente una interfaz. Por ejemplo, las estructuras del sistema integradas implementan interfaces como
IComparable
yIFormattable
.fuente
Hay muy pocas razones para que un tipo de valor implemente una interfaz. Dado que no puede subclasificar un tipo de valor, siempre puede referirse a él como su tipo concreto.
A menos que, por supuesto, tenga varias estructuras que implementen la misma interfaz, entonces podría ser marginalmente útil, pero en ese momento recomendaría usar una clase y hacerlo bien.
Por supuesto, al implementar una interfaz, está encajonando la estructura, por lo que ahora se encuentra en el montón y ya no podrá pasarla por valor ... Esto realmente refuerza mi opinión de que solo debe usar una clase en esta situación.
fuente
IComparable
para marcar el valor. Simplemente llamando a un método que esperaIComparable
con un tipo de valor que lo implementa, implícitamente encuadrará el tipo de valor.IComparable<T>
invocar métodos en estructuras de tipoT
sin recuadro.Las estructuras son como clases que viven en la pila. No veo ninguna razón por la que deberían ser "inseguros".
fuente