【问题标题】:Is there a generic constructor with parameter constraint in C#?C# 中是否有带有参数约束的通用构造函数?
【发布时间】:2010-12-23 14:17:57
【问题描述】:

在 C# 中,您可以对泛型方法施加约束,例如:

public class A {

    public static void Method<T> (T a) where T : new() {
        //...do something...
    }

}

您指定T 应该有一个不需要参数的构造函数。我想知道是否有办法添加诸如“存在带有float[,] 参数的构造函数?”之类的约束。

以下代码无法编译:

public class A {

    public static void Method<T> (T a) where T : new(float[,] u) {
        //...do something...
    }

}

解决方法也有用吗?

【问题讨论】:

    标签: c# generics parameters constructor generic-constraints


    【解决方案1】:

    如你所见,你不能这样做。

    作为一种解决方法,我通常会提供一个可以创建 T 类型对象的委托:

    public class A {
    
        public static void Method<T> (T a, Func<float[,], T> creator) {
            //...do something...
        }
    
    }
    

    【讨论】:

    • 参数化构造函数约束是否因逻辑原因而缺失,或者它只是尚未添加到语言中的东西?
    • 同意...我们应该有new(float, double)new(string)
    • @Matthew 并不是每个类都有无参构造函数,如果你定义了带参数的构造函数,并且没有重新定义默认构造函数,那么没有默认构造函数。
    • @Matthew 这就是泛型类型约束的重点。您需要一个类,它派生自某个类并包含具有特定参数的构造函数
    • @bc3tech,从技术上讲,您的观点并非 100% 正确。如果基类没有默认构造函数,则必须提供调用基类构造函数之一的构造函数。您不必提供匹配的构造函数。这里有一个微妙的区别......
    【解决方案2】:

    使用反射创建泛型对象,该类型仍需要声明正确的构造函数,否则将引发异常。您可以传入任何参数,只要它们与构造函数之一匹配即可。

    使用这种方式,您不能对模板中的构造函数施加约束。 如果缺少构造函数,则需要在运行时处理异常,而不是在编译时出错。

    // public static object CreateInstance(Type type, params object[] args);
    
    // Example 1
    T t = (T)Activator.CreateInstance(typeof(T));
    // Example 2
    T t = (T)Activator.CreateInstance(typeof(T), arg0, arg1, arg2, ...);
    // Example 3
    T t = (T)Activator.CreateInstance(typeof(T), (string)arg0, (int)arg1, (bool)arg2);
    

    【讨论】:

      【解决方案3】:

      没有这样的结构。您只能指定一个空的构造函数约束。

      我使用 lambda 方法解决了这个问题。

      public static void Method<T>(Func<int,T> del) {
        var t = del(42);
      }
      

      用例

      Method(x => new Foo(x));
      

      【讨论】:

      • Method内部没有办法抽象Foo的创建?
      • 如果Method 的用户使用Method(x =&gt; new Foo()); 怎么办?有没有办法确保 lambda 应该是这样的?
      • 在这种情况下提供委托而不是返回 int 并让消费者包装它有什么好处?感觉就像是没有任何收获的额外样板。
      【解决方案4】:

      这是我个人认为非常有效的解决方法。如果您想到什么是泛型参数化构造函数约束,它实际上是类型和具有特定签名的构造函数之间的映射。您可以使用字典创建自己的此类映射。将它们放在静态“工厂”类中,您可以创建不同类型的对象,而不必担心每次都构建构造函数 lambda:

      public static class BaseTypeFactory
      {
         private delegate BaseType BaseTypeConstructor(int pParam1, int pParam2);
      
         private static readonly Dictionary<Type, BaseTypeConstructor>
         mTypeConstructors = new Dictionary<Type, BaseTypeConstructor>
         {
            { typeof(Object1), (pParam1, pParam2) => new Object1(pParam1, pParam2) },
            { typeof(Object2), (pParam1, pParam2) => new Object2(pParam1, pParam2) },
            { typeof(Object3), (pParam1, pParam2) => new Object3(pParam1, pParam2) }
         };
      

      然后在你的泛型方法中,例如:

         public static T BuildBaseType<T>(...)
            where T : BaseType
         {
            ...
            T myObject = (T)mTypeConstructors[typeof(T)](value1, value2);
            ...
            return myObject;
         }
      

      【讨论】:

      • 我现在正在使用这个,我认为这是一个很好的模式。非常适合工厂模式。谢谢!
      • 这也可以扩展为基于其他数据创建类型。在解析类似 IFF 的文件时,我经常使用这种类型的构造。我更喜欢在类型本身上定义静态构造函数,所以我的字典条目最终看起来像 ["CELL"] = Cell.CreateInstance, ["WRLD"] = World.CreateInstance, ...
      【解决方案5】:

      没有。目前,您可以指定的唯一构造函数约束是无参数构造函数。

      【讨论】:

        【解决方案6】:

        我认为这是对对象的构造方式施加约束的最干净的解决方案。它不是完全编译时检查的。当您同意使类的实际构造函数具有与 IConstructor 接口相同的签名时,这有点像对构造函数有约束。由于显式接口实现,Constructor 方法在正常使用对象时是隐藏的。

        using System.Runtime.Serialization;
        
        namespace ConsoleApp4
        {
            class Program
            {
                static void Main(string[] args)
                {
                    var employeeWorker = new GenericWorker<Employee>();
                    employeeWorker.DoWork();
                }
            }
        
            public class GenericWorker<T> where T:IConstructor
            {
                public void DoWork()
                {
                    T employee = (T)FormatterServices.GetUninitializedObject(typeof(T));
                    employee.Constructor("John Doe", 105);
                }
            }
        
            public interface IConstructor
            {
                void Constructor(string name, int age);
            }
        
            public class Employee : IConstructor
            {
                public string Name { get; private set; }
                public int Age { get; private set; }
        
                public Employee(string name, int age)
                {
                    ((IConstructor)this).Constructor(name, age);
                }
        
                void IConstructor.Constructor(string name, int age)
                {
                    Name = name;
                    Age = age;
                }
            }
        }
        

        【讨论】:

        • +1 用于提供一些其他人不提供的编译时安全性,以及提供其他人不提供的接口支持。
        【解决方案7】:

        如何创建带有约束的泛型类,这里我选择了结构和类来具有值和引用类型。

        这样你的构造函数对值有约束。

        class MyGenericClass<T, X> where T :struct where X: class 
        {
            private T genericMemberVariableT;
            private X genericMemberVariableX;
            public MyGenericClass(T valueT, X valueX)
            {
                genericMemberVariableT = valueT;
                genericMemberVariableX = valueX;
            }
        
            public T genericMethod(T genericParameter)
            {
                Console.WriteLine("Parameter type: {0}, value: {1}", typeof(T).ToString(), genericParameter);
                Console.WriteLine("Return type: {0}, value: {1}", typeof(T).ToString(), genericMemberVariableT);
                Console.WriteLine("Return type: {0}, value: {1}", typeof(X).ToString(), genericMemberVariableX);
                return genericMemberVariableT;
            }
        
            public T genericProperty { get; set; }
        }
        

        实施:

                MyGenericClass<int, string> intGenericClass = new MyGenericClass<int, string>(10, "Hello world");
                int val = intGenericClass.genericMethod(200);
        

        【讨论】:

          【解决方案8】:

          以下是 c# 维护者推荐的解决方法,如果您想保持构造函数参数化,请间接调用构造函数:

                      i = (TService)Activator.CreateInstance(typeof(TService), new object[] {arg});
          

          其中 TService 是一个泛型,带有我想保留的全参数构造函数。

          如果您想了解此方法的工作原理: https://docs.microsoft.com/en-us/dotnet/api/system.activator.createinstance?view=net-5.0#system-activator-createinstance(system-type-system-object-)

          Aaaa 和 C# 维护者的讨论: https://github.com/dotnet/csharplang/discussions/769

          【讨论】:

          • 注意 Activator.CreateInstance 很慢
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多