2010-03-11 13 views
49

Wystąpił inny problem przy użyciu C# 4.0 z opcjonalnymi parametrami.Wywoływanie metod z opcjonalnymi parametrami za pomocą refleksji

Jak wywołać funkcję (a raczej konstruktora, mam obiekt ConstructorInfo), o którym wiem, że nie wymaga żadnych parametrów?

Oto kod używam teraz:

type.GetParameterlessConstructor() 
    .Invoke(BindingFlags.OptionalParamBinding | 
      BindingFlags.InvokeMethod | 
      BindingFlags.CreateInstance, 
      null, 
      new object[0], 
      CultureInfo.InvariantCulture); 

(Właśnie próbowałem z innym BindingFlags).

GetParameterlessConstructor jest niestandardową metodą rozszerzenia, którą napisałem dla Type.

Odpowiedz

106

Według MSDN, aby użyć parametru domyślnego należy przekazać Type.Missing.

Jeśli twój konstruktor ma trzy opcjonalne argumenty, to zamiast przepuścić pustą tablicę obiektów, przeszedłbyś przez trój elementową tablicę obiektów, w której wartość każdego elementu wynosi Type.Missing, np.

type.GetParameterlessConstructor() 
    .Invoke(BindingFlags.OptionalParamBinding | 
      BindingFlags.InvokeMethod | 
      BindingFlags.CreateInstance, 
      null, 
      new object[] { Type.Missing, Type.Missing, Type.Missing }, 
      CultureInfo.InvariantCulture); 
+3

Ta odpowiedź jest w rzeczywistości lepsza niż ta oznaczona jako właściwa! –

+0

Mogę potwierdzić, że to działa. Zgadzam się, że jest to lepsze rozwiązanie w większości przypadków i prawdopodobnie powinno zostać oznaczone jako takie. – N8allan

+0

Czy można po prostu wywołać "Invoke" (bez parametrów) na wynikowe "MethodInfo" lub "ConstructorInfo"? – Alxandr

22

Parametry opcjonalne są oznaczone zwykłym atrybutem i są obsługiwane przez kompilator.
Nie mają one wpływu (poza flagą metadanych) na IL i nie są bezpośrednio obsługiwane przez odbicie (z wyjątkiem właściwości IsOptional i DefaultValue).

Jeśli chcesz użyć opcjonalnych parametrów z refleksją, musisz ręcznie przekazać wartości domyślne.

+0

Dziękuję. Wymyśliłem rozwiązanie, które dokładnie to robi. To nie wygląda tak pięknie, ale działa. Ponadto IsOptional nie jest jedyną właściwością, DefaultValue również istnieje, więc po prostu buduję tablicę ze wszystkich DefaultValues. – Alxandr

1

W ramach opensource ImpromptuInterface od wersji 4 można użyć DLR w C# 4.0 do wywoływania konstruktorów w A very late bound way i to całkowicie świadomy konstruktorów o nazwie/opcjonalnych argumentów, to skończy 4 razy szybciej niż Activator.CreateInstance(Type type, params object[] args) i don” t muszą odzwierciedlać wartości domyślne.

using ImpromptuInterface; 
using ImpromptuInterface.InvokeExt; 

...

//if all optional and you don't want to call any 
Impromptu.InvokeConstructor(type) 

lub

//If you want to call one parameter and need to name it 
Impromptu.InvokeConstructor(type, CultureInfo.InvariantCulture.WithArgumentName("culture")) 
3

Dodam tylko kod ... ponieważ. Kod nie jest przyjemny, zgadzam się, ale jest dość prosty. Mam nadzieję, że pomoże to komuś, kto potknie się o to. Jest sprawdzony, choć zapewne nie tak dobrze jak byś chciał w środowisku produkcyjnym:

wywołanie metody methodName na obiekt obj argumentami args:

public Tuple<bool, object> Evaluate(IScopeContext c, object obj, string methodName, object[] args) 
    { 
     // Get the type of the object 
     var t = obj.GetType(); 
     var argListTypes = args.Select(a => a.GetType()).ToArray(); 

     var funcs = (from m in t.GetMethods() 
        where m.Name == methodName 
        where m.ArgumentListMatches(argListTypes) 
        select m).ToArray(); 

     if (funcs.Length != 1) 
      return new Tuple<bool, object>(false, null); 

     // And invoke the method and see what we can get back. 
     // Optional arguments means we have to fill things in. 
     var method = funcs[0]; 
     object[] allArgs = args; 
     if (method.GetParameters().Length != args.Length) 
     { 
      var defaultArgs = method.GetParameters().Skip(args.Length) 
       .Select(a => a.HasDefaultValue ? a.DefaultValue : null); 
      allArgs = args.Concat(defaultArgs).ToArray(); 
     } 
     var r = funcs[0].Invoke(obj, allArgs); 
     return new Tuple<bool, object>(true, r); 
    } 

a ArgumentListMatches funkcja jest poniżej, które zasadniczo bierze miejsce logiki prawdopodobnie znalezione w GetMethod:

public static bool ArgumentListMatches(this MethodInfo m, Type[] args) 
    { 
     // If there are less arguments, then it just doesn't matter. 
     var pInfo = m.GetParameters(); 
     if (pInfo.Length < args.Length) 
      return false; 

     // Now, check compatibility of the first set of arguments. 
     var commonArgs = args.Zip(pInfo, (margs, pinfo) => Tuple.Create(margs, pinfo.ParameterType)); 
     if (commonArgs.Where(t => !t.Item1.IsAssignableFrom(t.Item2)).Any()) 
      return false; 

     // And make sure the last set of arguments are actually default! 
     return pInfo.Skip(args.Length).All(p => p.IsOptional); 
    } 

Wiele LINQ, a to nie zostało przetestowane pod kątem wydajności!

Nie obsługuje to również ogólnych funkcji ani wywołań metod. To sprawia, że ​​jest to znacznie bardziej brzydkie (jak w przypadku wielokrotnych wywołań GetMethod).

1

Wszystkie pytania znikają jak widać kod dekompilowana:

C#:

public MyClass([Optional, DefaultParameterValue("")]string myOptArg) 

MSIL:

.method public hidebysig specialname rtspecialname instance void .ctor([opt]string myOptArg) cil managed 

Jak widać, opcjonalny parametr jest prawdziwy osobny podmiot, który jest urządzony o określonych atrybutach i musi być odpowiednio respektowany podczas wywoływania poprzez odbicie, jak opisano wcześniej.

Powiązane problemy