2009-08-20 9 views
14

Possiedo un'applicazione di origine chiusa di terze parti che esporta un'interfaccia COM, che sto utilizzando nell'applicazione C# .NET tramite Interop. Questa interfaccia COM esporta molti oggetti che vengono visualizzati come System.Object fino a quando non li lancio nel tipo di interfaccia appropriato. Voglio assegnare una proprietà di tutti questi oggetti. Così:funzione generica con un vincolo "ha proprietà X"?

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

Ma assegnando la proprietà è probabile (per ragioni specifiche per l'applicazione che sono inevitabili) per lanciare eccezioni da cui voglio recuperare, quindi voglio davvero un try/catch intorno ad ogni uno. Così:

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... 
    } 
} 

Ovviamente, sarebbe molto più pulito di fare questo:

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... 
    } 
} 

vedere il problema? Il mio valore x può essere di qualsiasi tipo, pertanto il compilatore non può risolvere .attribute. Chicken and Duck non sono in alcun tipo di albero ereditario e non condividono un'interfaccia che ha .attribute. Se lo facessero, potrei mettere un vincolo per quell'interfaccia su T. Ma dal momento che la classe è closed-source, non è possibile per me.

Quello che voglio, nella mia fantasia, è qualcosa di simile a un vincolo che richiede l'argomento di avere la proprietà .attribute indipendentemente dal fatto che implementa una determinata interfaccia. Vale a dire,

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

io non sono sicuro di cosa fare da qui se non per tagliare/incollare questo piccolo blocco try/catch per ciascuno di pollo, anatra, Vacca, Ovino, e così via.

La mia domanda è: che cosa è una buona soluzione per questo problema di voler richiamare una proprietà specifica su un oggetto quando l'interfaccia che implementa quella proprietà non può essere conosciuta in fase di compilazione?

risposta

4

Sfortunatamente, questo è al momento difficile. In C# 4, il tipo dinamico può aiutare un bel po 'con questo. L'interoperabilità COM è uno dei luoghi in cui la dinamica brilla davvero.

Tuttavia, nel frattempo, l'unica opzione che consente di avere qualsiasi tipo di oggetto, senza restrizioni sulle interfacce, sarebbe quella di ripristinare l'utilizzo di reflection.

È possibile utilizzare la riflessione per trovare la proprietà "attributo" e impostarne il valore in fase di esecuzione.

+0

Grazie, Reed. Ho provato la tecnica di Reflection - fa ciò di cui ho bisogno ma è un po 'lento. – catfood

+0

Dato che non esiste un'interfaccia comune, non c'è modo di fare il richiamo generico veloce basato su vtable - devi fare una ricerca dinamica sul nome, e questo è sempre lento, Riflessione o no. –

+0

@Pavel: Sì, è sempre lento, ma tecnicamente, è l'unico modo che funziona senza modificare le classi esistenti, fino a quando C# 4 non è disponibile. Non sto dicendo che è buono, ma piuttosto che funzionerebbe. –

2

Sfortunatamente l'unico modo per farlo è vincolare il parametro type con un'interfaccia che definisce tale proprietà ed è implementata su tutti i tipi.

Dal momento che non si dispone della fonte, sarà impossibile implementarlo e, come tale, sarà necessario utilizzare una soluzione alternativa. C# è tipizzato in modo statico e in quanto tale non supporta il tipo di digitazione anatra che si desidera utilizzare qui. La cosa migliore in arrivo (in C# 4) sarebbe quella di digitare l'oggetto come dynamic e risolvere i richiami di proprietà in fase di esecuzione (si noti che questo approccio non sarebbe anche generico in quanto non è possibile limitare un parametro di tipo generico come dynamic).

+3

È possibile farlo tramite riflessione. –

+0

@Reed - Buon punto! –

+0

Con dynamic, non è necessario il parametro generico. Basta usare un singolo metodo che prende il valore come dinamico e provare a impostare obj.attribute. Dovrebbe funzionare bene, a condizione che "attributo" esista come una proprietà su obj. –

6

Beh, a seconda di come humongous il codice di gestione delle eccezioni è (e se non mi sbaglio potrebbe essere così) utilizzando il seguente trucco potrebbe aiutare:

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

Questa è in definitiva la soluzione migliore. Le classi wrapper ti consentono di prendere il codice di terze parti e applicare le tue relazioni di ereditarietà, ecc. Inoltre ti isolerà dalle modifiche all'API di terze parti. – plinth

+1

Questa è una buona soluzione in generale, ma il mio problema originale è che un'applicazione closed-source di terze parti mi sta consegnando Chickens and Ducks di COM via Interop, racchiusa in System.Objects. Potrei avvolgerli in ChickenWrappers e DuckWrappers come suggerisci, ma per farlo dovrei passare attraverso * tutti * gli oggetti disponibili, provare a lanciare su Duck, Chicken, Swan, Goose, ecc., E poi avvolgere il risultato in DuckWrapper, ChickenWrapper, SwanWrapper o GooseWrapper, ecc. Questa sarebbe una dichiarazione di interruttore orribile. – catfood

+0

In realtà, guarda il costruttore Wrapper: Prende un oggetto e fa il casting lì, quindi faresti solo "nuovo ChickenWrapper (o)" invece di "(Chicken) o" –

2

di non dover violare la DRY principio puoi usare la riflessione. Se si desidera veramente utilizzare i generici, è possibile utilizzare una classe wrapper per ogni tipo di oggetto di terze parti e fare in modo che il wrapper implementa un'interfaccia che è possibile utilizzare per limitare l'argomento di tipo generico.

Un esempio di come si potrebbe fare con la riflessione. Il codice non è stato testato però.

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

Se ad accelerare il codice per le prestazioni, si potrebbe memorizzare nella cache l'FieldInfo e PropertyInfo per ogni tipo di chickenDuckOrWhatever e consultare il dizionario prima di riflettere.

SUGGERIMENTO: per non dovere codificare lo attributeName come stringa, è possibile utilizzare la funzione C# 6 nameof. Ad esempio, come nameof (Chicken.AttributeName).

+0

Quel TIP è eccellente. – catfood

Problemi correlati