【问题标题】:Instancing a class with an internal constructor使用内部构造函数实例化一个类
【发布时间】:2010-11-15 00:51:57
【问题描述】:

我有一个类,其构造函数定义为内部,这意味着我无法实例化它。虽然这可能是有道理的,但出于调试和研究目的,我仍然想这样做一次。

反射可以做到这一点吗?我知道我可以访问私有/内部成员,但我可以调用内部构造函数吗?

或者,由于构造函数没有做任何重要的事情,我可以使用反射说“看,给我一个类的实例而不调用构造函数,我会手动完成它的工作”?

性能和“稳定性”在这里不是问题,因为它不是生产代码。

编辑: 澄清一下:遗憾的是,我不控制其他程序集,也没有它的源代码,我只是试图了解它是如何工作的,因为它的文档几乎没有- 存在,但我应该与它交互。

【问题讨论】:

    标签: c# .net reflection


    【解决方案1】:

    另一种方法是将调用程序集指定为“朋友”程序集。

    只需将此添加到包含内部构造函数的程序集的 AssemblyInfo.cs 文件中:

    [assembly: InternalsVisibleTo("Calling.Assembly")]
    

    如果你无权访问程序集,你也可以直接调用构造函数(使用反射):

    MyClass obj = (MyClass) typeof(MyClass).GetConstructor(
                      BindingFlags.NonPublic | BindingFlags.Instance,
                      null, Type.EmptyTypes, null).Invoke(null);
    

    【讨论】:

    • 遗憾的是我无法控制其他程序集 :(
    • 编辑了我的答案以包含使用反射的解决方案
    • 出于某种原因,GetContructor 返回 null,即使在试验 BindingFlags 时也是如此。但通常这是一个有用的答案,所以 +1。
    • 为什么是'BindingFlags.Instance'?构造函数是实例成员吗?
    【解决方案2】:

    存在FormatterServices.GetUninitializedObject 方法(命名空间:System.Runtime.Serialization),如果您真的想尝试这种方法,它应该不会调用构造函数。

    【讨论】:

    • 这和 Type.GetField()/FieldInfo.SetValue 解决了我的问题。
    • @Kenan :如果构造函数接受参数并对这些参数进行操作,最好使用反射并调用它,而不是创建一个单元化对象并使用反射从“外部”实例复制构造函数行为。在我看来,Tore 解决方案更好。
    【解决方案3】:

    这是从this answer派生的方法:

    public static T CreateInstance<T>(params object[] args)
    {
        var type = typeof (T);
        var instance = type.Assembly.CreateInstance(
            type.FullName, false,
            BindingFlags.Instance | BindingFlags.NonPublic,
            null, args, null, null);
        return (T) instance;
    }
    

    使用示例(这是我需要为单元测试创​​建的 Kinect SDK 类型):

    DiscreteGestureResult a = CreateInstance<DiscreteGestureResult>(false, false, 0.5f);
    

    【讨论】:

      【解决方案4】:

      如果你想避免反射,你可以使用表达式。 这是一个使用字符串值调用私有构造函数的示例。

          private static Func<string, T> CreateInstanceFunc()
          {
              var flags = BindingFlags.NonPublic | BindingFlags.Instance;
              var ctor = typeof(T).GetConstructors(flags).Single(
                  ctors =>
                  {
                      var parameters = ctors.GetParameters();
                      return parameters.Length == 1 && parameters[0].ParameterType == typeof(string);
                  });
              var value = Expression.Parameter(typeof(string), "value");
              var body = Expression.New(ctor, value);
              var lambda = Expression.Lambda<Func<string, T>>(body, value);
      
              return lambda.Compile();
          }
      

      【讨论】:

      • 如何避免反射? typeof(T).GetConstructors(flags).
      【解决方案5】:

      不久前我遇到了同样的情况,并创建了一个名为“InternalsVisibleToInjector”的小实用程序。它使用 ILDASM 和 ILASM 来拆卸、修改、重新组装和组装,并将我选择的程序集名称添加到目标程序集的 InternalsVisibleTo 列表中。在我的情况下效果很好。

      我已经在此处发布了该实用程序的源代码(VS 2008 C# WinForm):

      http://www.schematrix.com/downloads/InternalsVisibleToInjector.zip

      如果程序集已签名,这可能不起作用。请采取所有适当的预防措施(即在使用之前备份组件并确保您有坚实的法律依据等)

      【讨论】:

        【解决方案6】:

        这里有一个更实际的例子。我想从实体框架实例化一个ObjectMaterializedEventArg。它看起来像这样:

        namespace System.Data.Entity.Core.Objects
        {
            /// <summary>EventArgs for the ObjectMaterialized event.</summary>
            public class ObjectMaterializedEventArgs : EventArgs
            {
                private readonly object _entity;
        
                internal ObjectMaterializedEventArgs(object entity)
                {
                    this._entity = entity;
                }
        
                /// <summary>Gets the entity object that was created.</summary>
                /// <returns>The entity object that was created.</returns>
                public object Entity
                {
                    get { return this._entity; }
                }
            }
        }
        

        正如我们所见,此事件 arg 仅包含一个内部构造函数。

        要在我使用的软件系统中对患者解密规则进行单元测试,我们需要实例化这样一个对象,因此我最终改用了 GetConstructors 方法。

        [Test]
        public void EmptyNameAndOfficialIdDoesNotThrow()
        {
            var patientDecryptingRule = new PatientDecryptingRule();
            object[] reservation = new object[]
            {
                new Operation
                {
                    Status = (int) OperationStatusDataContract.Reservation,
                    Patient = new Patient
                    {
                        Name = null,
                        OfficialId = null,
                        IsPatientEncrypted = true
                    }
                }
            };
        
            var relevantConstructor = typeof(ObjectMaterializedEventArgs).GetConstructors(
                BindingFlags.NonPublic | BindingFlags.Instance).FirstOrDefault();
        
            ObjectMaterializedEventArgs objectMaterializedEventArgs =
                (ObjectMaterializedEventArgs) relevantConstructor?.Invoke(reservation);
        
            patientDecryptingRule.ModifyObjectMaterialized(objectMaterializedEventArgs);
        }
        

        我在这里使用GetConstructors,指定要查找的构造函数是非公共的(例如内部的)和实例为bindingflags,然后使用FirsOrDefault

        希望此方案对无法正确获取GetConstructor 的人有所帮助。您可以改用GetConstructors 并在必要时进行更多过滤。然后在?.Invoke() 中传入参数的对象数组。

        【讨论】:

          【解决方案7】:

          您可以使用Reflector 分析其源代码并从中找出内部工作原理。

          【讨论】:

          • @AdamPlocher - 哈哈。 OP 明确指出“我只是试图了解它是如何工作的”。从 IL 对代码进行逆向工程,恕我直言,这是一种非常可行的理解代码的方式。我想说,为了理解某些程序集的内部工作原理,阅读代码比尝试在运行时戳实例更容易。所以我的答案是成立的,不管是否被否决。
          • 和@AdamPlocher:我 5 岁的答案与这个问题的所有其他答案一样古老。嘿,连问题都是5岁!那是怎么回事?
          【解决方案8】:

          如果有人再次遇到这个问题,这里有一个如何通过反射来提高它的示例:

          var args = FormatterServices.GetUninitializedObject(typeof(SizeChangedEventArgs)) as SizeChangedEventArgs;
          Debug.Assert(args != null);
          
          var field = typeof(SizeChangedEventArgs).GetField("_previousSize", BindingFlags.Instance | BindingFlags.NonPublic);
          Debug.Assert(field != null);
          field.SetValue(args, new Size(0,0));
          field = typeof(SizeChangedEventArgs).GetField("_element", BindingFlags.Instance | BindingFlags.NonPublic);
          Debug.Assert(field != null);
          field.SetValue(args, GraphicsWrapper);
          field = typeof(RoutedEventArgs).GetField("_source", BindingFlags.Instance | BindingFlags.NonPublic);
          Debug.Assert(field != null);
          field.SetValue(args, GraphicsWrapper);
          field = typeof(RoutedEventArgs).GetField("_routedEvent", BindingFlags.Instance | BindingFlags.NonPublic);
          Debug.Assert(field != null);
          field.SetValue(args, SizeChangedEvent);
          
          GraphicsWrapper.RaiseEvent(args);
          

          ...GraphicsWrapper 是您希望从中引发它的 WPF 控件。

          【讨论】:

          • B/c...不可能调用内部 ctor。因此上面的例子。使用 ACCEPTED 答案中的 FormatterServices.GetUninitializedObject() 方法,您可以绕过此限制。
          • 对不起,我没注意。 +1
          • 感谢@MartinPrikryl 的重访。谢谢!
          【解决方案9】:

          internal 并不意味着你不能实例化它。这只是意味着只有来自同一程序集的成员才能调用它。

          出于测试目的,您可以使用 InternalsVisibleTo 属性允许您的测试程序集访问内部组件。见http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.internalsvisibletoattribute.aspx

          【讨论】:

          • 呃.. 如果你想从另一个程序集实例化它怎么办。那你就不能……
          • 正确,除非您可以使用 InternalsVisibleTo。随着问题的更新,情况似乎并非如此,但我从原始措辞中并不清楚。
          猜你喜欢
          • 1970-01-01
          • 2022-01-21
          • 1970-01-01
          • 1970-01-01
          • 2014-04-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多