【问题标题】:Create instance of class by string, but take implicit conversions into account - C# Reflection按字符串创建类的实例,但要考虑隐式转换 - C# 反射
【发布时间】:2016-03-08 14:51:20
【问题描述】:

我需要在给定类型(作为字符串)和构造函数参数数组的情况下创建一个对象实例。

我就是这样实现的:

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

这在大多数情况下都可以正常工作,但存在问题;它不考虑隐式转换。


让我解释一下我的意思,假设我们有一个简单的类,其中隐式转换为 int 定义

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

我们有一个使用 int 作为构造函数参数的类

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

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

现在说我们要创建一个TestClass的实例,我们可以这样做: new TestClass(5)。在这种情况下,我们使用构造函数指定的确切参数类型 (int)。但是,我们也可以使用 ImplicitTest 类创建该类的实例,例如:new TestClass(new ImplicitTest(5.1))。这是可行的,因为参数从 ImplicitTest 隐式转换为 int。但是 Activator.CreateInstance() 不这样做。


我们可以使用我们之前的Create(string Name, params object[] Args) 方法来创建一个TestClass 的实例:Create("ThisNamespace.TestClass", 5),这样可以。 我遇到的问题是尝试使用隐式转换不起作用,因此这个 sn-p 会引发错误:Create("ThisNamespace.TestClass", new ImplicitTest(5.1))

我完全不知道如何考虑这一点,但这对我的用例很重要。也许我缺少 Activator.CreateInstance() 函数的某些参数,或者我可以使用完全不同的方法来实现我的目标?我一直找不到任何答案。


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

为什么?

【问题讨论】:

  • 可能有帮助的类似线程:stackoverflow.com/questions/20932208/…
  • 因为implicit operator intstatic,所以它不会覆盖object类的任何方法。而Activator.CreateInstance Args 你传递的是objects
  • 可以利用 .NET 的动态基础架构。 Microsoft.CSharp 程序集具有用于重载选择的 C#“规则”。见ideone.com/UkGm4E。我不够专业,无法将其转换为“通用”工作方法(动态不是我的 C# 领域)
  • @xanatos 这实际上看起来很有希望,非常感谢!

标签: c# .net reflection constructor implicit-conversion


【解决方案1】:

隐式类型转换是 C# 编译器的功能,而不是 CLR。​​

因此,如果您愿意使用反射,则必须手动实现。

【讨论】:

  • 我要补充一点,这是Binder 类的任务(在ConstructorInfo GetConstructor(BindingFlags bindingAttr, Binder binder, ... 中使用)。可悲的是没有CSharpBinder“在互联网上”
【解决方案2】:

你愿意在你的课堂上实现 IConvertible 吗?
您不必实现所有方法,只需实现您需要的方法即可。
如果是,您可以使用以下内容。 (你可以让它更通用,这样你就不必像我现在那样搜索特定的构造函数了)

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

【讨论】:

    【解决方案3】:

    我不是 C# 动态部分的专家,但这似乎可以正常工作:

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

    它利用了 .NET 的“动态”部分。它使用“了解”C# 绑定规则的Microsoft.CSharp.RuntimeBinder。正如其他人所指出的,用户定义的隐式和显式转换是 C# 的东西,而不是 .NET 的东西,因此您需要一个“了解”它们的活页夹。完美的解决方案是拥有“知道”C# 规则的System.Reflection.Binder 类的子类。然后你可以将它传递给Type.GetConstructor 并快乐地生活。遗憾的是没有这样的实现。

    请注意,可悲的是,委托存储在CallSiteCallSite&lt;T&gt; 的通用子类中,T 是委托类型。出于这个原因,大的switch 指令。

    代码的ideone:https://ideone.com/NoQ67H。注意,Mono 有怪癖,所以 0 参数构造函数有特殊处理。

    【讨论】:

      【解决方案4】:

      您可以利用 Expressions 框架,这里是一个应该做“技巧”的示例:

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

      可能有更快的方法可以做到这一点,但我不是很擅长表达...

      【讨论】:

      • 是的,很容易看出你没有经常使用Expressions - 大多数人只是构建一个表达式树,而不是将所有中间体存储在本地:D 很明显你是还没有受到函数式编程的很大影响。无论如何,这对于几乎任何时候需要反射都是一个很好的解决方案,同时还可以利用编译器通常处理的内容(例如装箱、隐式转换、重载解析......)。虽然我只会在只有一个构造函数可供“选择”时才真正使用它,因为像这样选择正确的构造函数有点模棱两可。
      • 谢谢,在这种情况下,我决定使用中间变量,因为在我看来它更具可读性
      • 是的,它带有“不习惯函数式编程”。试着玩一下 FP,你会感觉舒服很多。当 C# 随着时间的推移变得越来越实用(委托、表达式树、LINQ 等)时,这很重要。关键是纯函数允许您单独推理各个函数调用,因此“跳过”中间函数并不一定会使可读性变差。但实际上,这只是一个风格问题,没有什么太重要的。不过,FP 对您的工具集来说是一个很棒的工具 :)
      • @Luaan 是的,很容易看出你不经常使用表达式我写了很多Expressions,我确实将所有内容存储在一个单独的变量中。另一种方法是让代码行从我的表格左侧到表格右侧:-)
      • @xanatos 好吧,典型的解决方案是使用多行表达式 :) 但是,是的,这是一种风格选择。我经常在开始受 FP 影响的程序员而不是老式的命令式程序员中看到它。函数式程序 all 只是一个表达式(理论上)可能会有所帮助 - 使用中间体看起来只是试图让代码看起来很必要,即使它不是。您是否还在本地存储部分(但不是动态)LINQ 查询?还是您使用“流利的语法”?基本上是等价的。
      猜你喜欢
      • 2017-08-04
      • 2016-09-14
      • 1970-01-01
      • 1970-01-01
      • 2020-07-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多