2016-03-08 15 views
6

Znajduję się w sytuacji, w której potrzebuję utworzyć instancję obiektu, biorąc pod uwagę typ (jako ciąg znaków) i tablicę argumentów konstruktora.Utwórz instancję klasy według ciągu znaków, ale uwzględnij niejawne konwersje - C# Reflection

ten sposób to osiągnąć, że:

public object Create(string Name, params object[] Args) 
{ 
    return Activator.CreateInstance(Type.GetType(Name), Args); 
} 

Działa to dobrze w większości przypadków, ale nie ma problemu z nim; nie bierze pod uwagę niejawnych konwersji.


Pozwól mi wyjaśnić, co mam na myśli, że mamy prostą klasę z niejawna konwersja na int określonym

public class ImplicitTest 
{ 
    public double Val { get; set; } 

    public ImplicitTest(double Val) 
    { 
     this.Val = Val; 
    } 

    public static implicit operator int(ImplicitTest d) 
    { 
     return (int)d.Val; 
    } 
} 

i mamy klasę, która używa int jak to parametr konstruktora

public class TestClass 
{ 
    public int Val { get; set; }   

    public TestClass(int Val) 
    { 
     this.Val = Val; 
    } 
} 

Teraz mówimy, że chcemy zrobić instancję TestClass, możemy zrobić: new TestClass(5). W tym przypadku używamy dokładnego typu parametru, który konstruktor określa (int). Możemy jednak również utworzyć instancję klasy przy użyciu naszej klasy ImplicitTest, jako takiej: new TestClass(new ImplicitTest(5.1)). Działa to, ponieważ parametr jest niejawnie konwertowany z ImplicitTest na int. Activator.CreateInstance() jednak tego nie robi.


Możemy użyć naszego Create(string Name, params object[] Args) metody sprzed aby wystąpienie TestClass jako takie: Create("ThisNamespace.TestClass", 5), to działa. Problem mam przeżywa to, że stara się wykorzystać niejawne konwersje nie działa, dlatego ten fragment zgłasza błąd: Create("ThisNamespace.TestClass", new ImplicitTest(5.1))

mam absolutnie żadnego pojęcia, jak wziąć to pod uwagę, ale to jest bardzo ważne dla mojego przypadku użycia. Być może jest jakiś parametr funkcji Activator.CreateInstance(), której mi brakuje, a może jest zupełnie inna metoda, której mogę użyć, aby osiągnąć mój cel? Nie byłem w stanie znaleźć żadnych odpowiedzi.


TL; DR

//Valid 
new TestClass(5); 

//Valid 
new TestClass(new ImplicitTest(5.1)); 

//Valid 
Activator.CreateInstance(Type.GetType("ThisNamespace.TestClass"), 5); 

//Invalid, throws System.MissingMethodException 
Activator.CreateInstance(Type.GetType("ThisNamespace.TestClass"), new ImplicitTest(5.1)); 

Dlaczego?

+0

Podobny wątek, który może pomóc: http://stackoverflow.com/questions/20932208/activator-createinstance-not-working-for-implicit-cast-scenario –

+1

Ponieważ 'niejawny operator int' to' statyczny' , więc nie zastępuje żadnej metody klasy 'object'. A z 'Activator.CreateInstance'' Args', które przechodzisz to 'object's –

+0

Możliwe jest wykorzystanie dynamicznej infrastruktury .NET. Zestaw 'Microsoft.CSharp' ma" reguły "C# do wyboru przeciążenia. Zobacz https://ideone.com/UkGm4E. Nie jestem ekspertem na tyle, aby przetłumaczyć go na "ogólną" metodę pracy (dynamiczne nie jest moim polem C#). – xanatos

Odpowiedz

2

Konwersja typu niejawnego jest funkcją kompilatora C#, a nie CLR.

Dlatego jeśli chcesz korzystać z Reflection, musisz to zrobić ręcznie.

+0

Dodam, że jest to zadanie klasy 'Binder' (używane w' ConstructorInfo GetConstructor (BindingFlags bindingAttr, Binder binder, ... ') Niestety, nie ma" CSharpBinder "w Internecie" – xanatos

0

Czy chcesz wdrożyć aplikację IConvertible w swojej klasie?
Nie musisz wdrażać wszystkich metod, tylko tych, których potrzebujesz.
Jeśli tak, możesz użyć czegoś podobnego. (Można uczynić go bardziej ogólny, dzięki czemu nie trzeba szukać konkretnego konstruktora jak ja teraz)

using System; 
using System.Reflection; 

namespace ConsoleApplication 
{ 
    class Program 
    { 
     static void Main() 
     { 
      var theType = Type.GetType("ConsoleApplication.TestClass"); 

      ConstructorInfo ctor = theType.GetConstructors()[0]; 
      var argumentType = ctor.GetParameters()[0].ParameterType; 

      object contructorArgument = new ImplicitTest(5.1); 

      object instance = ctor.Invoke(new object[] { Convert.ChangeType(contructorArgument, argumentType) }); 

      Console.ReadLine(); 
     } 
    } 

    public class TestClass 
    { 
     public int Val { get; set; } 

     public TestClass(int Val) 
     { 
      this.Val = Val; 
     } 

    } 

    public class ImplicitTest : IConvertible 
    { 
     public double Val { get; set; } 

     public ImplicitTest(double Val) 
     { 
      this.Val = Val; 
     } 

     public static implicit operator int(ImplicitTest d) 
     { 
      return (int)d.Val; 
     } 

     public int ToInt32(IFormatProvider provider) 
     { 
      return (int)this; 
     } 

     //Other methods of IConvertible 
    } 
} 
0

Nie jestem ekspertem w dynamicznej części C#, ale to wydaje się działać poprawnie :

using System; 
using System.Linq; 
using System.Runtime.CompilerServices; 
using Microsoft.CSharp.RuntimeBinder; // Requires reference to Microsoft.CSharp 

public class ImplicitTest 
{ 
    public double Val { get; set; } 

    public ImplicitTest(double val) 
    { 
     this.Val = val; 
    } 

    public static implicit operator int(ImplicitTest d) 
    { 
     return (int)d.Val; 
    } 
} 

public class TestClass 
{ 
    public int Val { get; set; } 

    public TestClass(int val = 5) 
    { 
     this.Val = val; 
    } 

    public TestClass(int val1, int val2) 
    { 
     this.Val = val1 + val2; 
    } 

    public TestClass(int val1, int val2, int val3, int val4, int val5, int val6, int val7, int val8, int val9, int val10, int val11, int val12, int val13, int val14) 
    { 
     this.Val = val1 + val2 + val3 + val4 + val5 + val6 + val7 + val8 + val9 + val10 + val11 + val12 + val13 + val14; 
    } 
} 

public static class DynamicFactory 
{ 
    private static readonly CallSiteBinder callsiteBinder0 = Binder.InvokeConstructor(
     CSharpBinderFlags.None, 
     typeof(DynamicFactory), 
     // It is OK to have too many arguments :-) 
     new CSharpArgumentInfo[] 
     { 
      CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.IsStaticType | CSharpArgumentInfoFlags.UseCompileTimeType, null), // 0 parameters 
     }); 

    private static readonly CallSiteBinder callsiteBinder = Binder.InvokeConstructor(
     CSharpBinderFlags.None, 
     typeof(DynamicFactory), 
     // It is OK to have too many arguments :-) 
     // Note that this "feature" doesn't work correctly with Mono in the 
     // case of 0 arguments 
     new CSharpArgumentInfo[] 
     { 
      CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.IsStaticType | CSharpArgumentInfoFlags.UseCompileTimeType, null), // 0 parameters 
      CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), // 1 parameter 
      CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), // 2 parameters 
      CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), 
      CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), 
      CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), 
      CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), 
      CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), 
      CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), 
      CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), 
      CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), 
      CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), 
      CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), 
      CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), 
      CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), // 14 parameters 
     }); 

    // Quirk of Mono with 0 arguments. See callsiteBinder0 
    private static readonly CallSite<Func<CallSite, Type, object>> CallSite0 = CallSite<Func<CallSite, Type, object>>.Create(callsiteBinder0); 

    private static readonly CallSite<Func<CallSite, Type, object, object>> CallSite1 = CallSite<Func<CallSite, Type, object, object>>.Create(callsiteBinder); 
    private static readonly CallSite<Func<CallSite, Type, object, object, object>> CallSite2 = CallSite<Func<CallSite, Type, object, object, object>>.Create(callsiteBinder); 
    private static readonly CallSite<Func<CallSite, Type, object, object, object, object>> CallSite3 = CallSite<Func<CallSite, Type, object, object, object, object>>.Create(callsiteBinder); 
    private static readonly CallSite<Func<CallSite, Type, object, object, object, object, object>> CallSite4 = CallSite<Func<CallSite, Type, object, object, object, object, object>>.Create(callsiteBinder); 
    private static readonly CallSite<Func<CallSite, Type, object, object, object, object, object, object>> CallSite5 = CallSite<Func<CallSite, Type, object, object, object, object, object, object>>.Create(callsiteBinder); 
    private static readonly CallSite<Func<CallSite, Type, object, object, object, object, object, object, object>> CallSite6 = CallSite<Func<CallSite, Type, object, object, object, object, object, object, object>>.Create(callsiteBinder); 
    private static readonly CallSite<Func<CallSite, Type, object, object, object, object, object, object, object, object>> CallSite7 = CallSite<Func<CallSite, Type, object, object, object, object, object, object, object, object>>.Create(callsiteBinder); 
    private static readonly CallSite<Func<CallSite, Type, object, object, object, object, object, object, object, object, object>> CallSite8 = CallSite<Func<CallSite, Type, object, object, object, object, object, object, object, object, object>>.Create(callsiteBinder); 
    private static readonly CallSite<Func<CallSite, Type, object, object, object, object, object, object, object, object, object, object>> CallSite9 = CallSite<Func<CallSite, Type, object, object, object, object, object, object, object, object, object, object>>.Create(callsiteBinder); 
    private static readonly CallSite<Func<CallSite, Type, object, object, object, object, object, object, object, object, object, object, object>> CallSite10 = CallSite<Func<CallSite, Type, object, object, object, object, object, object, object, object, object, object, object>>.Create(callsiteBinder); 
    private static readonly CallSite<Func<CallSite, Type, object, object, object, object, object, object, object, object, object, object, object, object>> CallSite11 = CallSite<Func<CallSite, Type, object, object, object, object, object, object, object, object, object, object, object, object>>.Create(callsiteBinder); 
    private static readonly CallSite<Func<CallSite, Type, object, object, object, object, object, object, object, object, object, object, object, object, object>> CallSite12 = CallSite<Func<CallSite, Type, object, object, object, object, object, object, object, object, object, object, object, object, object>>.Create(callsiteBinder); 
    private static readonly CallSite<Func<CallSite, Type, object, object, object, object, object, object, object, object, object, object, object, object, object, object>> CallSite13 = CallSite<Func<CallSite, Type, object, object, object, object, object, object, object, object, object, object, object, object, object, object>>.Create(callsiteBinder); 
    private static readonly CallSite<Func<CallSite, Type, object, object, object, object, object, object, object, object, object, object, object, object, object, object, object>> CallSite14 = CallSite<Func<CallSite, Type, object, object, object, object, object, object, object, object, object, object, object, object, object, object, object>>.Create(callsiteBinder); 

    public static object Create(string typeName, params object[] args) 
    { 
     return Create(Type.GetType(typeName), args); 
    } 

    public static object Create(Type type, params object[] args) 
    { 
     if (type == null) 
     { 
      throw new ArgumentNullException("type"); 
     } 

     if (args == null) 
     { 
      args = new object[0]; 
     } 

     object obj; 

     switch (args.Length) 
     { 
      case 0: 
       // Quirk of Mono with 0 arguments. See callsiteBinder0 
       obj = CallSite0.Target(CallSite0, type); 
       break; 

      case 1: 
       obj = CallSite1.Target(CallSite1, type, args[0]); 
       break; 

      case 2: 
       obj = CallSite2.Target(CallSite2, type, args[0], args[1]); 
       break; 

      case 3: 
       obj = CallSite3.Target(CallSite3, type, args[0], args[1], args[2]); 
       break; 

      case 4: 
       obj = CallSite4.Target(CallSite4, type, args[0], args[1], args[2], args[3]); 
       break; 

      case 5: 
       obj = CallSite5.Target(CallSite5, type, args[0], args[1], args[2], args[3], args[4]); 
       break; 

      case 6: 
       obj = CallSite6.Target(CallSite6, type, args[0], args[1], args[2], args[3], args[4], args[5]); 
       break; 

      case 7: 
       obj = CallSite7.Target(CallSite7, type, args[0], args[1], args[2], args[3], args[4], args[5], args[6]); 
       break; 

      case 8: 
       obj = CallSite8.Target(CallSite8, type, args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]); 
       break; 

      case 9: 
       obj = CallSite9.Target(CallSite9, type, args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]); 
       break; 

      case 10: 
       obj = CallSite10.Target(CallSite10, type, args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9]); 
       break; 

      case 11: 
       obj = CallSite11.Target(CallSite11, type, args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10]); 
       break; 

      case 12: 
       obj = CallSite12.Target(CallSite12, type, args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11]); 
       break; 

      case 13: 
       obj = CallSite13.Target(CallSite13, type, args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12]); 
       break; 

      case 14: 
       obj = CallSite14.Target(CallSite14, type, args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13]); 
       break; 

      default: 
       throw new ArgumentException("Too many parameters"); 
     } 

     return obj; 
    } 
} 

public class Program 
{ 
    public static void Main() 
    { 
     try 
     { 
      Type monoRuntime = Type.GetType("Mono.Runtime"); 

      if (monoRuntime != null) 
      { 
       System.Reflection.MethodInfo displayName = monoRuntime.GetMethod("GetDisplayName", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); 

       if (displayName != null) 
       { 
        Console.WriteLine("Mono version {0}", displayName.Invoke(null, null)); 
       } 
      } 

      TestClass tc0 = (TestClass)DynamicFactory.Create("TestClass"); 
      TestClass tc1 = (TestClass)DynamicFactory.Create("TestClass", new ImplicitTest(1.0)); 
      TestClass tc1b = (TestClass)DynamicFactory.Create("TestClass", 1); 
      TestClass tc2 = (TestClass)DynamicFactory.Create("TestClass", new ImplicitTest(1.0), new ImplicitTest(2.0)); 
      TestClass tc14 = (TestClass)DynamicFactory.Create("TestClass", Enumerable.Range(0, 14).Select(x => new ImplicitTest((double)x)).ToArray()); 

      Console.WriteLine(tc0.Val); 
      Console.WriteLine(tc1.Val); 
      Console.WriteLine(tc1b.Val); 
      Console.WriteLine(tc2.Val); 
      Console.WriteLine(tc14.Val); 
     } 
     catch (Exception ex) 
     { 
      Console.WriteLine(ex); 
     } 
    } 
} 

Wykorzystuje "dynamiczną" część .NET.Korzysta z Microsoft.CSharp.RuntimeBinder, który "zna" wiążące reguły C#. Jak ktoś zauważył, zdefiniowane przez użytkownika niejawne i jawne rzuty są czymś w rodzaju C#, a nie .NET, więc potrzebujesz spinacza, który "wie" o nich. Idealnym rozwiązaniem byłoby posiadanie podklasy klasy System.Reflection.Binder, która "zna" reguły C#. Następnie możesz przekazać go do Type.GetConstructor i żyć szczęśliwy. Niestety nie ma takiej realizacji.

Należy zauważyć, że niestety delegat jest przechowywany w ogólnej podklasie o wartości CallSite, CallSite<T>, przy czym T jest typem delegowanym. Z tego powodu duża instrukcja switch.

Ideone kodu: https://ideone.com/NoQ67H. Zauważ, że w Mono występuje dziwactwo, więc konstruktorzy parametrów 0 mają specjalne możliwości.

0

Można wykorzystać ramach wyrazu, tutaj jest przykład, który powinien zrobić „trick”:

class Program 
{ 
    static object Create(string Name, params object[] Args) 
    { 
     Type theType = Type.GetType(Name); 
     object toReturn = null; 
     // Code wrote quickly, I just try to call all the type constructors... 
     foreach (var constructor in Type.GetType(Name).GetConstructors()) 
     { 
      try 
      { 
       string paramPrefix = "p"; 
       int pIdx = 0; 
       var expParamsConsts = new List<Expression>(); 
       var ctrParams = constructor.GetParameters(); 
       for (int i = 0; i < constructor.GetParameters().Length; i++) 
       { 
        var param = ctrParams[i]; 
        var tmpParam = Expression.Variable(param.ParameterType, paramPrefix + pIdx++); 
        var expConst = Expression.Convert(Expression.Constant(Args[i]), param.ParameterType); 
        expParamsConsts.Add(expConst); 
       } 
       // new Type(...); 
       var expConstructor = Expression.New(constructor, expParamsConsts); 
       // return new Type(...); 
       var expLblRetTarget = Expression.Label(theType); 
       var expReturn = Expression.Return(expLblRetTarget, expConstructor, theType); 
       var expLblRet = Expression.Label(expLblRetTarget, Expression.Default(theType)); 
       // { return new Type(...); } 
       var expBlock = Expression.Block(expReturn, expLblRet); 
       // Build the expression and run it 
       var expFunc = Expression.Lambda<Func<dynamic>>(expBlock); 
       toReturn = expFunc.Compile().Invoke(); 
      } 
      catch (Exception ex) 
      { 
       ex.ToString(); 
      } 
     } 
     return toReturn; 
    } 

    static void Main(string[] args) 
    { 
     var tmpTestClass = Create(TYPE_NAME, 5); 
     tmpTestClass = Create(TYPE_NAME, new ImplicitTest(5.1)); 
    } 
} 

public class ImplicitTest 
{ 
    public double Val { get; set; } 

    public ImplicitTest(double Val) 
    { 
     this.Val = Val; 
    } 

    public static implicit operator int(ImplicitTest d) 
    { 
     return (int)d.Val; 
    } 
} 

public class TestClass 
{ 
    public int Val { get; set; } 

    public TestClass(int Val) 
    { 
     this.Val = Val; 
    } 
} 

Prawdopodobnie istnieją szybszy sposób, aby to zrobić, ale nie jestem ekspertem w wyrażeniach ...

+0

Tak, łatwo zauważyć, że nie używasz często 'Wyrażeń '- większość ludzi po prostu buduje drzewo wyrażeń, zamiast przechowywać wszystkie półprodukty u mieszkańców: D To oczywiste, że nie jesteś zbytnio dotknięty przez programowanie funkcjonalne. Sprawa jest świetnym rozwiązaniem praktycznie przez cały czas, gdy potrzebujesz refleksji, a jednocześnie wykorzystuje to, co zwykle jest obsługiwane przez kompilator (np. boks, niejawne konwersje, rozdzielczość przeciążania ...). Chociaż tak naprawdę używałbym tego tylko wtedy, gdy jest tylko jeden konstruktor do "wyboru", ponieważ wybór właściwego konstruktora jest niejasny ... – Luaan

+0

Dzięki, w tym przypadku postanowiłem użyć zmiennych pośrednich, ponieważ wydawało mi się bardziej czytelne –

+1

Tak, pochodzi z "nieużywanego do programowania funkcjonalnego". Spróbuj trochę pograć z FP, a będziesz z nim o wiele wygodniejszy. Co jest ważne, gdy C# z czasem staje się coraz bardziej funkcjonalne (delegaci, drzewa ekspresji, LINQ, ...). Kluczową kwestią jest to, że czyste funkcje umożliwiają rozumowanie o pojedynczych wywołaniach funkcji w izolacji, więc "pomijanie" półproduktów niekoniecznie powoduje pogorszenie czytelności. Ale tak naprawdę to tylko kwestia stylistyczna, nic zbyt ważnego. FP jest doskonałym narzędziem dla twojego zestawu narzędzi, ale :) – Luaan

Powiązane problemy