2010-03-25 11 views
8

domanda aggiornato dato risposta corretta di Andrew Hare:Come faccio a enumerare un elenco di interfacce che sono direttamente definite su una classe ereditaria?

Date le seguenti classi C#:

public class Bar : Foo, IDisposable 
{ 
    // implementation of Bar and IDisposable 
} 

public class Foo : IEnumerable<int> 
{ 
    // implementation of Foo and all its inherited interfaces 
} 

voglio un metodo come la seguente che non manca sulle affermazioni (Nota: non è possibile cambiare le affermazioni):

public void SomeMethod() 
{ 
    // This doesn't work 
    Type[] interfaces = typeof(Bar).GetInterfaces(); 

    Debug.Assert(interfaces != null); 
    Debug.Assert(interfaces.Length == 1); 
    Debug.Assert(interfaces[0] == typeof(IDisposable)); 
} 
Can

qualcuno aiuto mediante la fissazione di questo metodo in modo che le asserzioni non fallire?

Calling typeof(Bar).GetInterfaces() non funziona perché restituisce l'intera gerarchia di interfaccia (cioè interfaces variabile contiene IEnumerable<int>, IEnumerable, e IDisposable), non solo il livello superiore.

+0

Perché mai dovresti farlo? Non sarebbe molto meglio e più pulito affermare semplicemente che gli attrezzi Bar sono IDisposable? – Svish

+0

@Svish - Questo è un esempio forzato. Sto lavorando a una struttura di auto-binding IoC personalizzata che ha bisogno di cercare la maggior parte delle interfacce. Ho pensato di semplificare lo scenario per rispondere alla domanda di root che cercavo, e non confondere il rumore IoC. Inoltre, ora quando qualcun altro ha bisogno di una risposta a questa domanda in un contesto diverso, il rumore IoC non si intrometterà. – Jordan

risposta

7

Prova questa:

using System.Linq;  
public static class Extensions 
{ 
    public static Type[] GetTopLevelInterfaces(this Type t) 
    { 
     Type[] allInterfaces = t.GetInterfaces(); 
     var selection = allInterfaces 
      .Where(x => !allInterfaces.Any(y => y.GetInterfaces().Contains(x))) 
      .Except(t.BaseType.GetInterfaces()); 
     return selection.ToArray(); 
    } 
} 

di utilizzo:

private void Check(Type t, Type i) 
    { 
     var interfaces = t.GetTopLevelInterfaces(); 

     Debug.Assert(interfaces != null, "interfaces is null"); 
     Debug.Assert(interfaces.Length == 1, "length is not 1"); 
     Debug.Assert(interfaces[0] == i, "the expected interface was not found"); 

     System.Console.WriteLine("\n{0}", t.ToString()); 
     foreach (var intf in interfaces) 
      System.Console.WriteLine(" " + intf.ToString()); 

    } 

    public void Run() 
    { 
     Check(typeof(Foo), typeof(IEnumerable<int>)); 
     Check(typeof(Bar), typeof(IDisposable)); 
    } 

Come notato altrove, questo funziona solo se il controllato tipo implementa esplicitamente una singola interfaccia. Se ne hai più di uno, devi modificare il tuo Assert.

2

Non c'è davvero alcun modo per fare questo in quanto si recupera tutte le interfacce dalla gerarchia di interfaccia. Ciò significa che quando implementi IEnumerable<T> stai anche implicitamente implementando IEnumerable.

In altre parole, se si guarda alla IL per la classe che hai creato si vedrà questo:

.class public auto ansi beforefieldinit Foo 
     extends [mscorlib]System.Object 
     implements [mscorlib]System.Collections.Generic.IEnumerable`1<int32>, 
        [mscorlib]System.Collections.IEnumerable 
{ 
    // ... 
} 

Anche se hai indicato soltanto che il tipo implementa IEnumerable<T>, il compilatore ha emesso IL che indica il tuo tipo implementa IEnumerable<T> e IEnumerable.

L'API di riflessione è felicemente tornando quello che hai effettivamente definito dal tipo (che è che il tipo implementa entrambe le interfacce - che in realtà fa). Il compilatore C# ti consente di fare riferimento solo al tipo più basso nella gerarchia dell'interfaccia, poiché riempirà le altre interfacce implementate anche dal tuo tipo. Questo è uno dei modi in cui l'ereditarietà dell'interfaccia differisce dall'ereditarietà del tipo.

+0

Andrew - la tua risposta era corretta; la mia domanda non era completa come doveva essere. Grazie! – Jordan

+0

In realtà è possibile filtrarli osservando il BaseType del tipo di interfaccia. Vedi la mia risposta altrove. –

0

avrei scritto come:

public void SomeMethod() 
{ 
    Type[] interfaces = typeof(Foo).GetInterfaces(); 
    Debug.Assert(interfaces.Contains(typeof(IEnumerable<int>))); 
} 

Ma è difficile rispondere senza sapere cosa si sta cercando di verificare. Indipendentemente da ciò, è necessario not rely on the order quando si utilizza GetInterfaces e tale metodo restituirà un array vuoto se il tipo non implementa alcun valore, quindi il controllo Null non è necessario.

Edit: se davvero non è possibile modificare le affermazioni, poi la cosa sicura da fare è:

 Type[] allInterfaces = typeof(Foo).GetInterfaces(); 
     var interfaces = allInterfaces.Where(x => x == typeof(IEnumerable<int>)).ToArray(); 

     Debug.Assert(interfaces != null); 
     Debug.Assert(interfaces.Length == 1); 
     Debug.Assert(interfaces[0] == typeof(IEnumerable<int>)); 
+0

Come lo scriveresti senza cambiare le asserzioni? – Jordan

+2

Non lo farei. La prima asserzione (controllo nullo) passerà sempre (anche se non è dannosa), la seconda ha senso solo se si implementano solo interfacce sotto il proprio controllo e la terza (accesso per indice) è specificamente messa in guardia nella documentazione. –

+0

true, ma ha usato specificatamente quelle asserzioni solo per il suo tipo Foo originale, che implementava esplicitamente solo una singola interfaccia. Il tuo "codice sicuro" è una tautologia. In qualche modo elude l'intera domanda. – Cheeso

2

Andrew Hare è corretto che non si può recuperare l'elenco specificato di interfacce utilizzando riflessione. Tuttavia puoi trovare le interfacce "di primo livello" escludendo tutte le interfacce implicite da altri. Potresti implementarlo in questo modo:

Type[] allInterfaces = typeof(Foo).GetInterfaces(); 
Type[] interfaces = allInterfaces 
    .Where(x => !allInterfaces.Any(y => y.GetInterfaces().Contains(x))) 
    .ToArray(); 

Ciò supera le tue affermazioni.

+0

Beh, è ​​stato ... finché non ha cambiato la sua domanda! #% !!%! –

+1

+1 perché * era * giusto a un certo punto ;-) –

+0

+1 - grazie per la risposta! – Jordan

2

Vuoi solo ottenere le interfacce di primo livello, giusto? Potresti mischiare alcuni LINQ e riflessioni; basta escludere tutto ciò che il tipo di base sta implementando.

var fooType = typeof(Foo); 

if(fooType.BaseType == null) 
    return fooType.GetInterfaces().ToArray(); 

return fooType 
    .GetInterfaces() 
    .Except(fooType.BaseType.GetInterfaces()) 
    .ToArray(); 
+0

Hah. Cosa ha detto Marco. Anche se ora vedo la risposta di Andrew, questo non è esattamente quello che stai chiedendo ma passa le tue affermazioni. –

+0

Questo funziona per questo caso ma può fallire se la classe implementa più di un'interfaccia perché GetInterface non le restituisce in un ordine coerente. –

+0

@Jamie, è vero, e direi che la domanda è mal posta. Non ha generalizzato al caso in cui ci sono più interfacce implementate dal tipo di toplevel. – Cheeso

0

Nota: aggiornato per filtrare anche le interfacce ereditate.

Si potrebbe escludere membri interfaccia di base, in questo modo:

public Type[] GetDeclaredInterfaces(Type type) 
{ 
    if(type == typeof(object)) 
     return new Type[ 0 ];  
    Type[] interfaces = type.GetInterfaces(); 
    Type[] baseInterfaces = interfaces.Where(i => i.BaseType != null && i.BaseType.IsInterface); 
    Type[] declaredInterfaces = interfaces.Except(type.BaseType.GetInterfaces()); 
    return declaredInterfaces.Except(baseInterfaces); 
} 
Problemi correlati