在 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...
}
}
解决方法也有用吗?
正如您所发现的,您不能这样做。
作为解决方法,我通常提供一个可以创建
T
类型的对象的委托:
public class A {
public static void Method<T> (T a, Func<float[,], T> creator) {
//...do something...
}
}
使用反射创建泛型对象,该类型仍然需要声明正确的构造函数,否则会抛出异常。您可以传入任何参数,只要它们与构造函数之一匹配即可。
使用这种方式你不能对模板中的构造函数施加约束。 如果缺少构造函数,则需要在运行时处理异常,而不是在编译时出现错误。
// 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);
不存在这样的构造。您只能指定一个空的构造函数约束。
我用 lambda 方法解决了这个问题。
public static void Method<T>(Func<int,T> del) {
var t = del(42);
}
用例
Method(x => new Foo(x));
这是一个我个人认为非常有效的解决方法。如果您考虑一下通用参数化构造函数约束是什么,它实际上是具有特定签名的类型和构造函数之间的映射。您可以使用字典创建自己的此类映射。将它们放入静态“工厂”类中,您可以创建不同类型的对象,而不必担心每次都构建构造函数 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;
}
不。目前,您可以指定的唯一构造函数约束是无参数构造函数。
我认为这是最干净的解决方案,对对象的构造方式施加了限制。它没有完全检查编译时间。当您同意使类的实际构造函数具有与 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;
}
}
}
如何创建带有约束的泛型类,这里我选择结构和类来具有值和引用类型。
这样你的构造函数就对值有约束。
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);
如果您想保留构造函数参数丰富,请间接调用构造函数,这是 C# 维护人员推荐的解决方法:
i = (TService)Activator.CreateInstance(typeof(TService), new object[] {arg});
TService 是一个泛型,带有我想保留的全参数构造函数。
Aaaa 以及 C# 维护者的讨论: https://github.com/dotnet/csharplang/discussions/769
作为替代方案(从 C# 9+ 开始),您可以定义具有“init”属性的接口,就像定义构造函数的参数一样。 一个主要好处是它适用于结构或类。
using System;
public class Program
{
public interface ITest
{
int a { init; }
}
public struct Test : ITest{
public int a { private get; init; }
public int b => a;
}
public static T TestFunction<T>() where T: ITest, new() {
return new(){ a = 123 };
}
public static void Main()
{
var t = TestFunction<Test>();
Console.WriteLine($"Hello World: {t.b}"); // Prints: Hello World: 123
}
}
从 C# 11 / .NET 7 开始,这可以通过对包含带有必要参数的静态抽象工厂创建方法的接口应用约束,然后在所有相关类型中实现该接口来实现。
例如,首先定义以下接口:
public interface ICreatable<TArgument, TResult>
{
public abstract static TResult Create(TArgument arg);
}
然后,在您的
Method<T>
中,如果您希望 T
有一个采用 2d float 数组的静态工厂方法,请按如下方式对其进行约束:
public class A
{
public static void Method<T> (T a) where T : ICreatable<float[,], T>
{
var t = T.Create(new [,] { { 1f, 2f }, {3f, 4f} });
//...do something...
}
}
最后,您传递给
Method<T>
的任何类型都需要实现 ICreatable<float[,], T>
,例如如下:
public partial class Matrix2DFloat : ICreatable<float[,], Matrix2DFloat>
{
readonly float[,] array;
public Matrix2DFloat(float[,] array) => this.array = array ?? throw new ArgumentNullException(nameof(array));
#region ICreatable<float[,], Matrix2DFloat> Members
public static Matrix2DFloat Create(float[,] arg) => new Matrix2DFloat(arg);
#endregion
}
演示小提琴在这里。