我如何使用接口作为C#genericstypes约束?

有没有办法得到下面的函数声明?

public bool Foo<T>() where T : interface; 

即。 其中T是一个接口types(类似于where T : classstruct )。

目前我已经解决了:

 public bool Foo<T>() where T : IBase; 

在哪里IBase被定义为一个空的接口,被所有我的自定义接口inheritance…不理想,但它应该工作…为什么你不能定义一个genericstypes必须是一个接口?

对于什么是值得的,我想这是因为Foo正在做reflection,它需要一个接口types…我可以作为一个普通的parameter passing它,并在函数本身进行必要的检查,但这似乎更types安全(和我想有一点更高性能,因为所有的检查都是在编译时完成的)。

你可以做的最接近(除了你的基本接口方法)是“ where T : class ”,意思是引用types。 没有语法来表示“任何接口”。

例如,在WCF中使用此(“ where T : class ”)来将客户端限制为服务契约(接口)。

我知道这有点晚,但对于那些有兴趣的人可以使用运行时检查。

 typeof(T).IsInterface 

不,实际上,如果你认为classstruct意味着classstruct ,你错了。 class意味着任何引用types (例如,也包括接口)和struct意味着任何值types (如structenum )。

为了跟上Robert的回答,这个更晚了,但是你可以使用一个静态的辅助类来为运行时检查每个types一次:

 public bool Foo<T>() where T : class { FooHelper<T>.Foo(); } private static class FooHelper<TInterface> where TInterface : class { static FooHelper() { if (!typeof(TInterface).IsInterface) throw // ... some exception } public static void Foo() { /*...*/ } } 

我也注意到,你的“应该工作”的解决scheme实际上不工作。 考虑:

 public bool Foo<T>() where T : IBase; public interface IBase { } public interface IActual : IBase { string S { get; } } public class Actual : IActual { public string S { get; set; } } 

现在没有什么能阻止你叫Foo了:

 Foo<Actual>(); 

毕竟, Actual课程满足IBase约束。

一段时间以来,我一直在考虑接近编译时间的限制,所以这是发布这个概念的好机会。

其基本思想是,如果你不能检查编译时间,你应该尽早完成,这基本上是应用程序启动的时刻。 如果所有检查都没有问题,应用程序将运行; 如果检查失败,应用程序将立即失败。

行为

最好的结果是如果不满足约束,我们的程序不会编译。 不幸的是,在当前的C#实现中是不可能的。

接下来最好的事情是,程序崩溃的一瞬间开始。

最后一个选项是程序在代码被击中的时候会崩溃。 这是.NET的默认行为。 对我而言,这是完全不能接受的。

Prerequirements

我们需要有一个约束机制,所以缺乏更好的东西…让我们使用一个属性。 该属性将出现在通用约束之上,以检查它是否符合我们的条件。 如果没有,我们会给出一个丑陋的错误。

这使我们可以在我们的代码中做这样的事情:

 public class Clas<[IsInterface] T> where T : class 

(我在这里保留了where T:class ,因为我总是喜欢编译时检查运行时检查)

所以,这只会给我们留下一个问题,即检查我们使用的所有types是否与约束匹配。 它能有多难?

让我们分解它

genericstypes总是在类(/结构/接口)或方法上。

触发约束需要您执行以下任一操作:

  1. 编译时,使用types中的types(inheritance,generics约束,类成员)
  2. 编译时,在方法体中使用types时
  3. 运行时,使用reflection来构build基于通用基类的东西。
  4. 运行时,使用reflection来构build基于RTTI的东西。

在这一点上,我想说,你应该总是避免(4)在任何程序海事组织。 无论如何,这些检查不会支持它,因为这将有效地解决停止问题。

案例1:使用一个types

例:

 public class TestClass : SomeClass<IMyInterface> { ... } 

例2:

 public class TestClass { SomeClass<IMyInterface> myMember; // or a property, method, etc. } 

基本上这涉及扫描所有types,inheritance,成员,参数等等等等。如果一个types是genericstypes并且有一个约束,我们检查这个约束; 如果它是一个数组,我们检查元素types。

在这一点上,我必须补充说,这将打破事实,即默认.NET加载types'懒'。 通过扫描所有types,我们强制.NET运行时加载它们。 对于大多数程序来说,这不应该是一个问题。 如果你在你的代码中使用静态初始化器,你可能会遇到这种方法的问题…也就是说,我不会build议任何人这样做(除了这样的事情:-),所以它不应该给你很多问题。

情况2:在方法中使用types

例:

 void Test() { new SomeClass<ISomeInterface>(); } 

要检查这个,我们只有一个select:反编译类,检查所有使用的成员标记,如果其中一个是generics​​types – 检查参数。

情况3:reflection,运行时通用构造

例:

 typeof(CtorTest<>).MakeGenericType(typeof(IMyInterface)) 

我想在理论上可以用case(2)类似的技巧来检查它,但是它的实现更加困难(你需要检查MakeGenericType是否在某些代码path中被调用)。 我不会在这里详细介绍…

情况4:reflection,运行时RTTI

例:

 Type t = Type.GetType("CtorTest`1[IMyInterface]"); 

这是最糟糕的情况,正如我以前解释一般不好的主意恕我直言。 无论哪种方式,使用检查都没有可行的方法。

testing很多

创build一个testing案例(1)和(2)的程序会产生如下的结果:

 [AttributeUsage(AttributeTargets.GenericParameter)] public class IsInterface : ConstraintAttribute { public override bool Check(Type genericType) { return genericType.IsInterface; } public override string ToString() { return "Generic type is not an interface"; } } public abstract class ConstraintAttribute : Attribute { public ConstraintAttribute() {} public abstract bool Check(Type generic); } internal class BigEndianByteReader { public BigEndianByteReader(byte[] data) { this.data = data; this.position = 0; } private byte[] data; private int position; public int Position { get { return position; } } public bool Eof { get { return position >= data.Length; } } public sbyte ReadSByte() { return (sbyte)data[position++]; } public byte ReadByte() { return (byte)data[position++]; } public int ReadInt16() { return ((data[position++] | (data[position++] << 8))); } public ushort ReadUInt16() { return (ushort)((data[position++] | (data[position++] << 8))); } public int ReadInt32() { return (((data[position++] | (data[position++] << 8)) | (data[position++] << 0x10)) | (data[position++] << 0x18)); } public ulong ReadInt64() { return (ulong)(((data[position++] | (data[position++] << 8)) | (data[position++] << 0x10)) | (data[position++] << 0x18) | (data[position++] << 0x20) | (data[position++] << 0x28) | (data[position++] << 0x30) | (data[position++] << 0x38)); } public double ReadDouble() { var result = BitConverter.ToDouble(data, position); position += 8; return result; } public float ReadSingle() { var result = BitConverter.ToSingle(data, position); position += 4; return result; } } internal class ILDecompiler { static ILDecompiler() { // Initialize our cheat tables singleByteOpcodes = new OpCode[0x100]; multiByteOpcodes = new OpCode[0x100]; FieldInfo[] infoArray1 = typeof(OpCodes).GetFields(); for (int num1 = 0; num1 < infoArray1.Length; num1++) { FieldInfo info1 = infoArray1[num1]; if (info1.FieldType == typeof(OpCode)) { OpCode code1 = (OpCode)info1.GetValue(null); ushort num2 = (ushort)code1.Value; if (num2 < 0x100) { singleByteOpcodes[(int)num2] = code1; } else { if ((num2 & 0xff00) != 0xfe00) { throw new Exception("Invalid opcode: " + num2.ToString()); } multiByteOpcodes[num2 & 0xff] = code1; } } } } private ILDecompiler() { } private static OpCode[] singleByteOpcodes; private static OpCode[] multiByteOpcodes; public static IEnumerable<ILInstruction> Decompile(MethodBase mi, byte[] ildata) { Module module = mi.Module; BigEndianByteReader reader = new BigEndianByteReader(ildata); while (!reader.Eof) { OpCode code = OpCodes.Nop; int offset = reader.Position; ushort b = reader.ReadByte(); if (b != 0xfe) { code = singleByteOpcodes[b]; } else { b = reader.ReadByte(); code = multiByteOpcodes[b]; b |= (ushort)(0xfe00); } object operand = null; switch (code.OperandType) { case OperandType.InlineBrTarget: operand = reader.ReadInt32() + reader.Position; break; case OperandType.InlineField: if (mi is ConstructorInfo) { operand = module.ResolveField(reader.ReadInt32(), mi.DeclaringType.GetGenericArguments(), Type.EmptyTypes); } else { operand = module.ResolveField(reader.ReadInt32(), mi.DeclaringType.GetGenericArguments(), mi.GetGenericArguments()); } break; case OperandType.InlineI: operand = reader.ReadInt32(); break; case OperandType.InlineI8: operand = reader.ReadInt64(); break; case OperandType.InlineMethod: try { if (mi is ConstructorInfo) { operand = module.ResolveMember(reader.ReadInt32(), mi.DeclaringType.GetGenericArguments(), Type.EmptyTypes); } else { operand = module.ResolveMember(reader.ReadInt32(), mi.DeclaringType.GetGenericArguments(), mi.GetGenericArguments()); } } catch { operand = null; } break; case OperandType.InlineNone: break; case OperandType.InlineR: operand = reader.ReadDouble(); break; case OperandType.InlineSig: operand = module.ResolveSignature(reader.ReadInt32()); break; case OperandType.InlineString: operand = module.ResolveString(reader.ReadInt32()); break; case OperandType.InlineSwitch: int count = reader.ReadInt32(); int[] targetOffsets = new int[count]; for (int i = 0; i < count; ++i) { targetOffsets[i] = reader.ReadInt32(); } int pos = reader.Position; for (int i = 0; i < count; ++i) { targetOffsets[i] += pos; } operand = targetOffsets; break; case OperandType.InlineTok: case OperandType.InlineType: try { if (mi is ConstructorInfo) { operand = module.ResolveMember(reader.ReadInt32(), mi.DeclaringType.GetGenericArguments(), Type.EmptyTypes); } else { operand = module.ResolveMember(reader.ReadInt32(), mi.DeclaringType.GetGenericArguments(), mi.GetGenericArguments()); } } catch { operand = null; } break; case OperandType.InlineVar: operand = reader.ReadUInt16(); break; case OperandType.ShortInlineBrTarget: operand = reader.ReadSByte() + reader.Position; break; case OperandType.ShortInlineI: operand = reader.ReadSByte(); break; case OperandType.ShortInlineR: operand = reader.ReadSingle(); break; case OperandType.ShortInlineVar: operand = reader.ReadByte(); break; default: throw new Exception("Unknown instruction operand; cannot continue. Operand type: " + code.OperandType); } yield return new ILInstruction(offset, code, operand); } } } public class ILInstruction { public ILInstruction(int offset, OpCode code, object operand) { this.Offset = offset; this.Code = code; this.Operand = operand; } public int Offset { get; private set; } public OpCode Code { get; private set; } public object Operand { get; private set; } } public class IncorrectConstraintException : Exception { public IncorrectConstraintException(string msg, params object[] arg) : base(string.Format(msg, arg)) { } } public class ConstraintFailedException : Exception { public ConstraintFailedException(string msg) : base(msg) { } public ConstraintFailedException(string msg, params object[] arg) : base(string.Format(msg, arg)) { } } public class NCTChecks { public NCTChecks(Type startpoint) : this(startpoint.Assembly) { } public NCTChecks(params Assembly[] ass) { foreach (var assembly in ass) { assemblies.Add(assembly); foreach (var type in assembly.GetTypes()) { EnsureType(type); } } while (typesToCheck.Count > 0) { var t = typesToCheck.Pop(); GatherTypesFrom(t); PerformRuntimeCheck(t); } } private HashSet<Assembly> assemblies = new HashSet<Assembly>(); private Stack<Type> typesToCheck = new Stack<Type>(); private HashSet<Type> typesKnown = new HashSet<Type>(); private void EnsureType(Type t) { // Don't check for assembly here; we can pass f.ex. System.Lazy<Our.T<MyClass>> if (t != null && !t.IsGenericTypeDefinition && typesKnown.Add(t)) { typesToCheck.Push(t); if (t.IsGenericType) { foreach (var par in t.GetGenericArguments()) { EnsureType(par); } } if (t.IsArray) { EnsureType(t.GetElementType()); } } } private void PerformRuntimeCheck(Type t) { if (t.IsGenericType && !t.IsGenericTypeDefinition) { // Only check the assemblies we explicitly asked for: if (this.assemblies.Contains(t.Assembly)) { // Gather the generics data: var def = t.GetGenericTypeDefinition(); var par = def.GetGenericArguments(); var args = t.GetGenericArguments(); // Perform checks: for (int i = 0; i < args.Length; ++i) { foreach (var check in par[i].GetCustomAttributes(typeof(ConstraintAttribute), true).Cast<ConstraintAttribute>()) { if (!check.Check(args[i])) { string error = "Runtime type check failed for type " + t.ToString() + ": " + check.ToString(); Debugger.Break(); throw new ConstraintFailedException(error); } } } } } } // Phase 1: all types that are referenced in some way private void GatherTypesFrom(Type t) { EnsureType(t.BaseType); foreach (var intf in t.GetInterfaces()) { EnsureType(intf); } foreach (var nested in t.GetNestedTypes()) { EnsureType(nested); } var all = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance; foreach (var field in t.GetFields(all)) { EnsureType(field.FieldType); } foreach (var property in t.GetProperties(all)) { EnsureType(property.PropertyType); } foreach (var evt in t.GetEvents(all)) { EnsureType(evt.EventHandlerType); } foreach (var ctor in t.GetConstructors(all)) { foreach (var par in ctor.GetParameters()) { EnsureType(par.ParameterType); } // Phase 2: all types that are used in a body GatherTypesFrom(ctor); } foreach (var method in t.GetMethods(all)) { if (method.ReturnType != typeof(void)) { EnsureType(method.ReturnType); } foreach (var par in method.GetParameters()) { EnsureType(par.ParameterType); } // Phase 2: all types that are used in a body GatherTypesFrom(method); } } private void GatherTypesFrom(MethodBase method) { if (this.assemblies.Contains(method.DeclaringType.Assembly)) // only consider methods we've build ourselves { MethodBody methodBody = method.GetMethodBody(); if (methodBody != null) { // Handle local variables foreach (var local in methodBody.LocalVariables) { EnsureType(local.LocalType); } // Handle method body var il = methodBody.GetILAsByteArray(); if (il != null) { foreach (var oper in ILDecompiler.Decompile(method, il)) { if (oper.Operand is MemberInfo) { foreach (var type in HandleMember((MemberInfo)oper.Operand)) { EnsureType(type); } } } } } } } private static IEnumerable<Type> HandleMember(MemberInfo info) { // Event, Field, Method, Constructor or Property. yield return info.DeclaringType; if (info is EventInfo) { yield return ((EventInfo)info).EventHandlerType; } else if (info is FieldInfo) { yield return ((FieldInfo)info).FieldType; } else if (info is PropertyInfo) { yield return ((PropertyInfo)info).PropertyType; } else if (info is ConstructorInfo) { foreach (var par in ((ConstructorInfo)info).GetParameters()) { yield return par.ParameterType; } } else if (info is MethodInfo) { foreach (var par in ((MethodInfo)info).GetParameters()) { yield return par.ParameterType; } } else if (info is Type) { yield return (Type)info; } else { throw new NotSupportedException("Incorrect unsupported member type: " + info.GetType().Name); } } } 

使用代码

那么,这是很容易的部分:-)

 // Create something illegal public class Bar2 : IMyInterface { public void Execute() { throw new NotImplementedException(); } } // Our fancy check public class Foo<[IsInterface] T> { } class Program { static Program() { // Perform all runtime checks new NCTChecks(typeof(Program)); } static void Main(string[] args) { // Normal operation Console.WriteLine("Foo"); Console.ReadLine(); } } 

你不能在任何发布的C#版本中,也不能在即将到来的C#4.0中这样做。 这不是C#限制,CLR本身没有“接口”约束。

如果可能的话,我去解决这个问题。 它只适用于你想要几个特定的​​接口(例如你有源代码访问的接口)作为一个通用parameter passing,而不是任何接口。

  • 我让我的接口出现问题,inheritance一个空接口IInterface
  • 我将genericsT参数约束为IInterface

在源代码中,它看起来像这样:

  • 您想要作为通用parameter passing的任何接口:

     public interface IWhatever : IInterface { // IWhatever specific declarations } 
  • IInterface:

     public interface IInterface { // Nothing in here, keep moving } 
  • 您想要放置types约束的类:

     public class WorldPeaceGenerator<T> where T : IInterface { // Actual world peace generating code } 

你已经解决的是你可以做的最好的:

 public bool Foo<T>() where T : IBase; 

我试图做类似的事情,并使用了一个解决方法:我想到隐式和显式运算符的结构:这个想法是包装在一个可以隐式转换为types的结构的types。

这是一个这样的结构:

公共结构InterfaceType {私人types_type;

 public InterfaceType(Type type) { CheckType(type); _type = type; } public static explicit operator Type(InterfaceType value) { return value._type; } public static implicit operator InterfaceType(Type type) { return new InterfaceType(type); } private static void CheckType(Type type) { if (type == null) throw new NullReferenceException("The type cannot be null"); if (!type.IsInterface) throw new NotSupportedException(string.Format("The given type {0} is not an interface, thus is not supported", type.Name)); } 

}

基本用法:

 // OK InterfaceType type1 = typeof(System.ComponentModel.INotifyPropertyChanged); // Throws an exception InterfaceType type2 = typeof(WeakReference); 

你必须想象你自己的机制,但是一个例子可能是一个在参数中使用InterfaceType而不是types的方法

 this.MyMethod(typeof(IMyType)) // works this.MyMethod(typeof(MyType)) // throws exception 

重写的方法应该返回接口types:

 public virtual IEnumerable<InterfaceType> GetInterfaces() 

也有可能与generics有关,但我没有尝试

希望这可以帮助或给出的想法:-)

改用抽象类。 所以,你会有这样的事情:

 public bool Foo<T>() where T : CBase;