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:
- usar el objeto resultante para fines de bloqueo (una idea inmensamente mala de cualquier manera)
- utilizando semántica de igualdad de referencia y esperando que funcione para dos valores en caja de la misma estructura.
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);
}
}
- Habilite el uso de la estructura como parámetro de tipo
- siempre que no se utilice ninguna otra restricción como
new()
o class
.
- Permita evitar el boxeo en estructuras utilizadas de esta manera.
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 :
Si thisType es un tipo de valor y thisType implementa el método, ptr se pasa sin modificar como el puntero 'this' a una instrucción de método de llamada, para la implementación del método por thisType.
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 de IEquatable<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 implementan IEquatable<T>
(dónde T
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 como Add()
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)
;
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ón Dispose()
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:
IL_0001: newobj System.Collections.Generic.List..ctor
IL_0006: stloc.0
IL_0007: nop
IL_0008: ldloc.0
IL_0009: callvirt System.Collections.Generic.List.GetEnumerator
IL_000E: stloc.2
IL_000F: br.s IL_0019
IL_0011: ldloca.s 02
IL_0013: llamar System.Collections.Generic.List.get_Current
IL_0018: stloc.1
IL_0019: ldloca.s 02
IL_001B: llamar System.Collections.Generic.List.MoveNext
IL_0020: stloc.3
IL_0021: ldloc.3
IL_0022: brtrue.s IL_0011
IL_0024: Leave.s IL_0035
IL_0026: ldloca.s 02
IL_0028: restringido. System.Collections.Generic.List.Enumerator
IL_002E: callvirt System.IDisposable.Dispose
IL_0033: nop
IL_0034: endfinally
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.