2013-05-07 10 views
6

Ecco una semplice applicazione che stampa la firma del metodo di un MethodCallExpression:MethodCallExpression.Method restituisce sempre MethodInfo della classe base di radice

using System; 
using System.Linq; 
using System.Linq.Expressions; 

class A 
{ 
    public virtual void Foo() { } 
} 

class B : A 
{ 
    public override void Foo() { } 
} 

class C : B 
{ 
    public override void Foo() { } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     PrintMethod<A>(a => a.Foo()); 
     PrintMethod<B>(b => b.Foo()); 
     PrintMethod<C>(c => c.Foo()); 

     Console.Read(); 
    } 

    static void PrintMethod<T>(Expression<Action<T>> expression) 
    { 
     var body = (MethodCallExpression)expression.Body; 

     var method1 = body.Method; 
     var method2 = typeof(T).GetMethod(body.Method.Name, body.Method.GetParameters().Select(p => p.ParameterType).ToArray()); 

     Console.WriteLine("body.Method   -> " + method1.DeclaringType.ToString() + " - " + method1.ToString()); 
     Console.WriteLine("typeof(T).GetMethod -> " + method2.DeclaringType.ToString() + " - " + method2.ToString()); 
    } 
} 

mi aspetterei il programma per stampare:

body.Method   -> A - Void Foo() 
typeof(T).GetMethod -> A - Void Foo() 
body.Method   -> B - Void Foo() * 
typeof(T).GetMethod -> B - Void Foo() 
body.Method   -> C - Void Foo() * 
typeof(T).GetMethod -> C - Void Foo() 

Ma stampa invece:

body.Method   -> A - Void Foo() 
typeof(T).GetMethod -> A - Void Foo() 
body.Method   -> A - Void Foo() * 
typeof(T).GetMethod -> B - Void Foo() 
body.Method   -> A - Void Foo() * 
typeof(T).GetMethod -> C - Void Foo() 

Quando si riceve il Method proprietà per l'ereditato MethodCallExpression, restituisce sempre A s MethodInfo (la classe radice).

Tuttavia, in Visual Studio e I "Vai a definizione" di ognuna delle chiamate Foo(), sono portato a ciascuno dei metodi sottoposti a override come previsto.

Perché il MethodCallExpression.Method si comporta in questo modo? C'è qualcosa nelle specifiche su questo? Perché c'è una discrepanza tra VS e la proprietà Method? Ho provato con .NET 4.0 e 4.5.

+0

Buona domanda. Questo comportamento sembra intuitivamente corretto se si pensa al codice che viene generato - in tutti e 3 i casi si tratta di un callvirt a 'A.Foo'. –

+0

Questo comportamento è stato menzionato nel repository C# Language Design su GitHub: vedere https://github.com/dotnet/roslyn/issues/24347#issuecomment-359070141. – stakx

risposta

4

Supponiamo di avere una libreria:

public class A 
{ 
    public virtual void Foo() { } 
} 

public class B : A 
{ 
    public override void Foo() { } 
} 

public class C : B 
{ 
    public override void Foo() { } 
} 

e avete un consumatore che fa

new C().Foo(); 

Ora si aggiorna la libreria in modo che C non ignora Foo:

public class C : B 
{ 
} 

Il consumatore deve essere ricompilato?

Se il consumatore chiama virtualmente C.Foo, sì, e il consumatore dovrebbe scrivere in modo specifico ((A)new C()).Foo() per evitare questo problema. Se il consumatore chiama virtualmente A.Foo, allora no. Poiché questa è l'unica differenza, poiché la stessa funzione verrà richiamata in fase di runtime, non ha senso che il consumatore specifichi che chiama C.Foo.

Gli alberi di espressione registrano le stesse informazioni sul metodo che una chiamata di funzione normale registrerebbe. La specifica C# ha molto poco da dire su questo, la lascia definita dall'implementazione (ma l'implementazione di Microsoft non sembra definirla):

La conversione di una funzione anonima in un tipo di albero di espressione produce un albero di espressioni (§4.6). Più precisamente, la valutazione della conversione della funzione anonima porta alla costruzione di una struttura oggetto che rappresenta la struttura della funzione anonima stessa. La struttura precisa dell'albero delle espressioni e il processo esatto per crearlo sono definiti dall'implementazione.

+0

Sì, questo ha senso ora che lo hai spiegato. Tuttavia, non è molto ovvio. Mi sarei aspettato che VS e l'albero delle espressioni si comportassero allo stesso modo. – TheCloudlessSky

+0

@TheCloudlessSky Concordato che non è ovvio, ma sono contento che gli sviluppatori VS abbiano fatto un'eccezione per far funzionare "Vai alla definizione" in modo più intuitivo di questo, anche se significa che si comporta in modo diverso da ciò che fa C#. :) – hvd