2009-08-20 9 views
14

Tengo una aplicación de código abierto de terceros que exporta una interfaz COM, que estoy usando en mi aplicación C# .NET a través de Interop. Esta interfaz COM exporta muchos objetos que se muestran como System.Object hasta que los transfiero al tipo de interfaz apropiado. Quiero asignar una propiedad de todos estos objetos. Por lo tanto:función genérica con una restricción "tiene la propiedad X"?

foreach (object x in BigComInterface.Chickens) 
{ 
    (x as Chicken).attribute = value; 
} 
foreach (object x in BigComInterface.Ducks) 
{ 
    (x as Duck).attribute = value; 
} 

Pero la asignación de la propiedad es probable que (por razones específicas de la aplicación que son inevitables) para lanzar excepciones de la que desea recuperar, por lo que realmente quiere un try/catch alrededor de cada uno. Por lo tanto:

foreach (object x in BigComInterface.Chickens) 
{ 
    try 
    { 
     (x as Chicken).attribute = value; 
    } 
    catch (Exception ex) 
    { 
     // handle... 
    } 
} 
foreach (object x in BigComInterface.Ducks) 
{ 
    try 
    { 
     (x as Duck).attribute = value; 
    } 
    catch (Exception ex) 
    { 
     // handle... 
    } 
} 

Obviamente, sería mucho más limpio para hacer esto:

foreach (object x in BigComInterface.Chickens) 
{ 
    SetAttribute<Chicken>(x as Chicken, value); 
} 
foreach (object x in BigComInterface.Ducks) 
{ 
    SetAttribute<Duck>(x as Duck, value); 
} 

void SetAttribute<T>(T x, System.Object value) 
{ 
    try 
    { 
     x.attribute = value; 
    } 
    catch 
    { 
     // handle... 
    } 
} 

ver el problema? Mi valor x puede ser de cualquier tipo, por lo que el compilador no puede resolver .tributo. Chicken y Duck no están en ningún tipo de árbol de herencia y no comparten una interfaz que tenga .tributo. Si lo hicieran, podría poner una restricción para esa interfaz en T. Pero dado que la clase es de código cerrado, eso no es posible para mí.

Lo que quiero, en mi fantasía, es algo así como una restricción que requiere que el argumento tenga la propiedad .attribute independientemente de si implementa una interfaz determinada. A saber,

void SetAttribute<T>(T x, System.Object value) where T:hasproperty(attribute) 

no estoy seguro de qué hacer a partir de aquí aparte de cortar/pegar este pequeño bloque try/catch para cada una de pollo, pato, vaca, oveja, y así sucesivamente.

Mi pregunta es: ¿Cuál es una buena solución para este problema de querer invocar una propiedad específica en un objeto cuando la interfaz que implementa esa propiedad no puede conocerse en tiempo de compilación?

Respuesta

4

Lamentablemente, esto es complicado actualmente. En C# 4, el tipo dinámico puede ayudar bastante con esto. La interoperabilidad COM es uno de los lugares que la dinámica realmente brilla.

Sin embargo, mientras tanto, la única opción que le permite tener cualquier tipo de objeto, sin restricciones en las interfaces, sería volver a utilizar la reflexión.

Puede usar el reflejo para encontrar la propiedad "atributo" y establecer su valor en tiempo de ejecución.

+0

Gracias, Reed. Probé la técnica de Reflexión: hace lo que necesito pero es un poco lento. – catfood

+0

Como no hay una interfaz común, no hay forma de hacer la invocación genérica basada en el vtable rápido: tiene que hacer una búsqueda dinámica en el nombre, y esto siempre es lento, Reflexión o no. –

+0

@Pavel: Sí, siempre es lento, pero técnicamente, es la única forma de que funcione sin cambiar las clases existentes, hasta que C# 4 se apague. No digo que sea bueno, sino que funcionaría. –

2

Desafortunadamente, la única manera de hacerlo es restringir el parámetro de tipo con una interfaz que define esa propiedad y se implementa en todos los tipos.

Dado que no tiene la fuente, esto será imposible de implementar y, como tal, tendrá que utilizar algún tipo de solución alternativa. C# está tipado estáticamente y, como tal, no es compatible con el tipo de pato-tipado que desea usar aquí. Lo mejor que llegará pronto (en C# 4) sería escribir el objeto como dynamic y resolver las llamadas a la propiedad en tiempo de ejecución (tenga en cuenta que este enfoque tampoco sería genérico ya que no puede restringir un parámetro de tipo genérico como dynamic).

+3

Puede hacerlo a través de la reflexión. –

+0

@Reed - ¡Buen punto! –

+0

Con dinámico, no necesita el parámetro genérico. Solo use un método único que tome el valor como dinámico e intente configurar obj.attribute. Debería funcionar bien, siempre que exista "atributo" como propiedad en obj. –

6

Bueno, dependiendo de cómo gigantescos su código de manejo de excepciones es (y si no me equivoco, podría ser tan) utilizando el siguiente truco te pueden ayudar:

class Chicken 
{ 
    public string attribute { get; set; } 
} 

class Duck 
{ 
    public string attribute { get; set; } 
} 

interface IHasAttribute 
{ 
    string attribute { get; set; } 
} 

class ChickenWrapper : IHasAttribute 
{ 
    private Chicken chick = null; 
    public string attribute 
    { 
     get { return chick.attribute; } 
     set { chick.attribute = value; } 
    } 
    public ChickenWrapper(object chick) 
    { 
     this.chick = chick as Chicken; 
    } 
} 

class DuckWrapper : IHasAttribute 
{ 
    private Duck duck = null; 
    public string attribute 
    { 
     get { return duck.attribute; } 
     set { duck.attribute = value; } 
    } 
    public DuckWrapper(object duck) 
    { 
     this.duck = duck as Duck; 
    } 
} 

void SetAttribute<T>(T x, string value) where T : IHasAttribute 
{ 
    try 
    { 
     x.attribute = value; 
    } 
    catch 
    { 
     // handle... 
    } 
} 
+0

Esta es finalmente la mejor solución. Las clases Wrapper le permiten tomar un código de terceros y aplicar sus propias relaciones de herencia, etc. También lo aislarán de los cambios en la API de terceros. – plinth

+1

Esta es una buena solución en general, pero mi problema original es que una aplicación de código cerrado de un tercero me está entregando Pollos y patos de COM a través de Interop, envuelto en System.Objects. Podría envolverlos en ChickenWrappers y DuckWrappers como sugieres, pero para hacer eso tendré que pasar por * todos * los objetos disponibles, intentar lanzar a Duck, Chicken, Swan, Goose, etc., y luego envolver el resultado en DuckWrapper, ChickenWrapper, SwanWrapper o GooseWrapper, etc. Esa sería una declaración de cambio horrible. – catfood

+0

En realidad, mira el constructor Wrapper: Toma un objeto y realiza el fundido allí, por lo que solo harías "nuevo ChickenWrapper (o)" en lugar de "(Chicken) o" –

2

para no tener que violar la SECO principio, puedes usar la reflexión. Si realmente desea usar genéricos, puede usar una clase de contenedor para cada tipo de objeto de terceros y hacer que el contenedor implemente una interfaz que puede usar para restringir el argumento de tipo genérico.

Un ejemplo de cómo se podría hacer con la reflexión. Sin embargo, el código no ha sido probado.

void SetAttribute(object chickenDuckOrWhatever, string attributeName, string value) 
    { 
     var typeOfObject = chickenDuckOrWhatever.GetType(); 
     var property = typeOfObject.GetProperty(attributeName); 
     if (property != null) 
     { 
      property.SetValue(chickenDuckOrWhatever, value); 
      return; 
     } 

     //No property with this name was found, fall back to field 
     var field = typeOfObject.GetField(attributeName); 
     if (field == null) throw new Exception("No property or field was found on type '" + typeOfObject.FullName + "' by the name of '" + attributeName + "'."); 
     field.SetValue(chickenDuckOrWhatever, value); 
    } 

Si a acelerar el código para el rendimiento, se podría almacenar en caché el FieldInfo y PropertyInfo según el tipo de chickenDuckOrWhatever y consultar el diccionario primero antes de reflexionar.

CONSEJO: Para no tener que codificar el attributeName como una cadena, puede usar la función C# 6 nameof. Como nameof (Chicken.AttributeName) por ejemplo.

+0

Ese TIP es excelente. – catfood

Cuestiones relacionadas