c# - programming - ¿Por qué este elenco no es posible?
programming paradigms pdf (4)
interface IFolderOrItem<TFolderOrItem> where TFolderOrItem : FolderOrItem {}
abstract class FolderOrItem {}
class Folder : FolderOrItem {}
abstract class Item : FolderOrItem {}
class Document : Item {}
ahora estoy tratando de hacer algo así:
class Something
{
IFolderItemOrItem<Item> SelectedItem { get; set; }
void SomeMagicMethod()
{
this.SelectedItem = (IFolderOrItem<Item>)GetMagicDocument();
// bad bad bad ... ??
}
IFolderOrItem<Document> GetMagicDocument()
{
return someMagicDocument; // which is of type IFolderOrItem<Document>
}
}
¿Hay alguna posibilidad de que esto funcione?
El problema es que un elenco no funciona en los argumentos genéricos, sino en la clase como un todo. El documento hereda de Item, true, pero IFolderOrItem <Document> no hereda de IFolderOrItem <Item>, ni está relacionado con él de ninguna manera.
En lo que respecta al compilador, IFolderOrItem<Document>
& IFolderOrItem<Item>
son dos tipos completamente diferentes.
Document
puede heredar Item
, pero IFolderOrItem<Document>
no hereda IFolderOrItem<Item>
Confío en Marc o Jon para publicar enlaces a las partes relevantes de la especificación C #.
Si lo leo correctamente ... entonces el problema es que solo porque Foo : Bar
, eso no significa que ISomething<Foo> : ISomething<Bar>
...
En algunos casos, la variación en C # 4.0 puede ser una opción. Alternativamente, a veces hay cosas que puedes hacer con métodos genéricos (aunque no estoy seguro de que te ayuden).
Lo más cercano que puede hacer en C # 3.0 (y abajo) es probablemente una interfaz base no genérica:
interface IFolderOrItem {}
interface IFolderOrItem<TFolderOrItem> : IFolderOrItem
where TFolderOrItem : FolderOrItem { }
Comúnmente, la interfaz base tendría, por ejemplo, un Type ItemType {get;}
para indicar el tipo real bajo consideración. Luego uso:
IFolderOrItem SelectedItem { get; set; }
...
public void SomeMagicMethod()
{
this.SelectedItem = GetMagicDocument(); // no cast needed
// not **so** bad
}
De la especificación, esto se relaciona con §25.5.6 (ECMA 334 v4):
25.5.6 Conversiones
Los tipos construidos siguen las mismas reglas de conversión (§13) que los tipos no genéricos. Al aplicar estas reglas, las clases de base y las interfaces de los tipos construidos se determinarán como se describe en §25.5.3.
No existen conversiones especiales entre los tipos de referencia construidos distintos de los descritos en el § 13. En particular, a diferencia de los tipos de matriz, los tipos de referencia construidos no permiten conversiones de variante (§19.5). Esto significa que un tipo
List<B>
no tiene conversión (implícita o explícita) aList<A>
incluso siB
se deriva deA
Del mismo modo, no existe una conversión deList<B>
aList<object>
.[Nota: La razón de esto es simple: si se permite una conversión a
List<A>
, aparentemente, uno puede almacenar valores de tipoA
en la lista. Sin embargo, esto rompería la invariante de que cada objeto en una lista de tipoList<B>
es siempre un valor de tipoB
, o pueden ocurrir fallas inesperadas al asignar a clases de colección. nota final]
Lo mismo se aplica a las interfaces. Esto cambia un poco en C # 4.0, pero solo en algunos casos.
Un ejemplo para entender por qué funciona de esta manera:
Supongamos que IFolderOrItem expone un método, por ejemplo, vacío Add (elemento T).
Su implementación para IFolderOrItem supondrá que el parámetro es un Documento.
Pero si usted lanza su IFolderOrItem como IFolderItemOrItem, entonces alguien podría llamar al método Create (T) donde se supone que T es un Item.
El lanzamiento de Elemento a documento no es válido, ya que un elemento no es un documento.
La única forma de hacerlo es crear una versión no genérica de la interfaz, permitiendo objetos como parámetros, el tipo de objeto en sus implementaciones.