usan una que puede programacion las interfaz interfaces instancia implementacion declaracion declara cómo crear como clase atributos abstracta c# interface struct

c# - una - ¿Es seguro para las estructuras implementar interfaces?



no se puede crear una instancia de la clase o interfaz abstracta c# (9)

Me parece recordar haber leído algo sobre la dificultad de las estructuras para implementar interfaces en CLR a través de C #, pero parece que no puedo encontrar nada al respecto. ¿Es mala? ¿Hay consecuencias involuntarias de hacerlo?

public interface Foo { Bar GetBar(); } public struct Fubar : Foo { public Bar GetBar() { return new Bar(); } }


(Bueno, no tengo nada importante que agregar, pero todavía no tengo destreza de edición, así que aquí va ...)
Perfectamente seguro. Nada ilegal con la implementación de interfaces en las estructuras. Sin embargo, debes preguntarte por qué querrías hacerlo.

Sin embargo, la obtención de una referencia de interfaz a una estructura lo RECOGE . Por lo tanto, pena de rendimiento, etc.

El único escenario válido en el que puedo pensar ahora se ilustra 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.


Como nadie más explícitamente proporcionó esta respuesta, agregaré lo siguiente:

Implementar una interfaz en una estructura no tiene consecuencias negativas de ningún tipo.

Cualquier variable del tipo de interfaz utilizada para contener una estructura dará como resultado un valor en caja de esa estructura que se está utilizando. Si la estructura es inmutable (algo bueno), este es, en el peor de los casos, un problema de rendimiento a menos que seas:

  • usar el objeto resultante para propósitos de bloqueo (una idea inmensamente mala de cualquier forma)
  • utilizando la semántica de igualdad de referencia y esperando que funcione para dos valores encuadrados de la misma estructura.

Ambos serían poco probables, en su lugar es probable que esté haciendo uno de los siguientes:

Genéricos

Quizás muchos motivos razonables para estructurar la implementación de interfaces es que se puedan usar dentro de un contexto genérico con constraints . Cuando se utiliza de esta manera la variable como tal:

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); } }

  1. Habilite el uso de struct como un parámetro de tipo
    • siempre que no se use ninguna otra restricción como new() o class .
  2. Permitir evitar el boxeo en las estructuras utilizadas de esta manera.

Entonces this.a NO es una referencia de interfaz por lo que no causa una caja de lo que sea que se coloque en ella. Además, cuando el compilador de c # compila las clases genéricas y necesita insertar invocaciones de los métodos de instancia definidos en las instancias del parámetro Tipo T, puede usar el código de operación constrained :

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 boxeo y como el tipo de valor está implementando la interfaz, debe implementar el método, por lo tanto, no habrá boxeo. En el ejemplo anterior, la invocación Equals() se realiza sin recuadro en this.a 1 .

API de baja fricción

La mayoría de las estructuras deben tener una semántica primitiva, donde los valores idénticos a nivel de bit se consideran iguales 2 . El tiempo de ejecución proporcionará dicho comportamiento en los Equals() pero esto puede ser lento. Además, esta igualdad implícita no está expuesta como una implementación de IEquatable<T> y, por lo tanto, impide que las estructuras se utilicen fácilmente como claves para 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> (donde T es ellos mismos) para hacer que esto sea más fácil y mejor, así como consistente con el comportamiento de muchos tipos de valores existentes dentro del CLR BCL.

Todas las primitivas en 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, la implementación de muchas de estas interfaces comunes (correctamente) hará que su estructura sea más útil y utilizable.

Exclusiones

Obviamente, si la interfaz implica una gran mutabilidad (como ICollection ), implementarla es una mala idea, ya que significaría que la estructura se modificó (lo que lleva a los tipos de errores descritos donde ocurren las modificaciones en el valor encuadrado en lugar del original) o puede confundir a los usuarios al ignorar las implicaciones de los métodos como Add() o lanzar excepciones.

Muchas interfaces NO implican mutabilidad (como IFormattable ) y sirven como la forma idiomática de exponer ciertas funciones de manera consistente. A menudo, al usuario de la estructura no le importará ninguna sobrecarga de boxeo para 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 cuales 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 consequences interesantes). Sin embargo, la semántica de foreach especifica que si el enumerador implementa IDisposable se IDisposable Dispose() una vez que se complete 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 disponer de llamada modifica el estado del enumerador de alguna forma, esto sucedería en la instancia encuadrada y podrían introducirse muchos errores sutiles en casos complejos. Por lo tanto, el IL emitido en este tipo de situación 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: call System.Collections.Generic.List.get_Current IL_0018: stloc.1 IL_0019: ldloca.s 02 IL_001B: call 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: constrained. 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 preserva 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.



En algunos casos, puede ser útil que una estructura implemente una interfaz (si nunca fue útil, es dudoso que los creadores de .net lo hubieran previsto). Si una estructura implementa una interfaz de solo lectura como IEquatable<T> , el almacenamiento de la estructura en una ubicación de almacenamiento (variable, parámetro, elemento de matriz, etc.) de tipo IEquatable<T> requerirá que esté encuadrada (cada tipo de estructura realmente define dos tipos de cosas: un tipo de ubicación de almacenamiento 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 - "boxeo" - y el segundo puede convertirse al primero a través del lanzamiento explícito - "unboxing"). Sin embargo, es posible explotar la implementación de una interfaz de una interfaz sin boxeo, utilizando lo que se denomina genéricos restringidos.

Por ejemplo, si uno tiene un método CompareTwoThings<T>(T thing1, T thing2) where T:IComparable<T> , dicho método podría llamar a thing1.Compare(thing2) sin tener que thing1 o thing2 . Si thing1 pasa a ser, por ejemplo, un Int32 , el tiempo de ejecución lo sabrá cuando genere el código para CompareTwoThings<Int32>(Int32 thing1, Int32 thing2) . Como sabrá el tipo exacto de la cosa que hospeda el método y lo 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 de tipo de interfaz, Object o ValueType (a diferencia de una ubicación de su propio tipo) se comportará como un objeto de clase. En el caso de las interfaces de solo lectura, esto no suele ser un problema, pero para una interfaz mutante como IEnumerator<T> puede generar 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 instrucción marcada n. ° 1 cebará el enumerator1 para leer el primer elemento. El estado de ese enumerador se copiará al enumerator2 . La instrucción marcada n. ° 2 hará avanzar esa copia para leer el segundo elemento, pero no afectará al enumerator1 . El estado de ese segundo enumerador se copiará al enumerator3 , que se avanzará mediante la declaración marcada n. ° 3. Entonces, como el enumerator3 y el enumerator4 son ambos tipos de referencia, una REFERENCIA al enumerator3 se copiará en el enumerator4 , por lo que la declaración marcada avanzará con eficacia tanto para el enumerator3 como para el enumerator4 .

Algunas personas intentan pretender que los tipos de valores y los tipos de referencia son ambos tipos de Object , pero eso no es realmente cierto. Los tipos de valores reales son convertibles a Object , pero no son instancias de este. Una instancia de List<String>.Enumerator que se almacena en una ubicación de ese tipo es un tipo de valor y se comporta como un tipo de valor; si lo copia a una ubicación de tipo IEnumerator<String> lo convertirá en un tipo de referencia, y se comportará como un tipo de referencia . Este último es un tipo de Object , 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 igualdad de referencia de prueba de métodos, pero no hay una manera 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 de System.Enum son tipos de valor, como todos los tipos que se derivan de ValueType con la excepción de System.Enum , pero tanto ValueType como System.Enum son tipos de clase.


Hay muy pocas razones para que un tipo de valor implemente una interfaz. Como no puede subclasificar un tipo de valor, siempre puede referirse a él como su tipo concreto.

A menos que, por supuesto, tenga múltiples estructuras, todas implementando la misma interfaz, podría ser marginalmente útil, pero en ese punto recomendaría usar una clase y hacerlo bien.

Por supuesto, al implementar una interfaz, estás encajonando la estructura, por lo que ahora está en el montón, y ya no podrás pasarla por valor ... Esto realmente refuerza mi opinión de que deberías simplemente usar una clase en esta situación.


Hay varias cosas que están pasando en esta pregunta ...

Es posible que una estructura implemente una interfaz, pero hay preocupaciones que surgen con la conversión, la mutabilidad y el rendimiento. Consulte esta publicación para obtener más información: http://blogs.msdn.com/abhinaba/archive/2005/10/05/477238.aspx

En general, las estructuras deberían usarse para objetos que tengan semántica de tipo de valor. Al implementar una interfaz en una estructura, puede encontrarse con problemas de boxeo a medida que la estructura se envía hacia adelante y hacia atrás entre la estructura y la interfaz. Como resultado del boxeo, las operaciones que cambian el estado interno de la estructura pueden no comportarse correctamente.


Las estructuras se implementan como tipos de valores y las clases son tipos de referencia. Si tiene una variable de tipo Foo, y almacena una instancia de Fubar en ella, la "Recuperará" en un tipo de referencia, derrotando así la ventaja de usar una estructura en primer lugar.

La única razón por la que veo 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 hereda una interfaz y pasa las interfaces, pierde la naturaleza del tipo de valor de la estructura. Bien podría ser una clase si necesita interfaces.


Las estructuras son como las clases que viven en la pila. No veo ninguna razón por la cual deberían ser "inseguros".


No hay consecuencias para una estructura que implementa una interfaz. Por ejemplo, el sistema incorporado estructura implementa interfaces como IComparable e IFormattable .