Is there a way to get the following function declaration?
有没有办法获得以下函数声明?
public bool Foo<T>() where T : interface;
ie. where T is an interface type (similar to where T : class
, and struct
).
即。其中T是接口类型(类似于T:class和struct)。
Currently I've settled for:
目前我已经满足于:
public bool Foo<T>() where T : IBase;
Where IBase is defined as an empty interface that is inherited by all my custom interfaces... Not ideal, but it should work... Why can't you define that a generic type must be an interface?
其中IBase被定义为一个空接口,由我的所有自定义接口继承...不理想,但它应该工作...为什么你不能定义泛型类型必须是一个接口?
For what it's worth, I want this because Foo
is doing reflection where it needs an interface type... I could pass it in as a normal parameter and do the necessary checking in the function itself, but this seemed a lot more typesafe (and I suppose a little more performant, since all the checks are done at compiletime).
对于它的价值,我想要这个,因为Foo正在做需要接口类型的反射...我可以将它作为普通参数传递并在函数本身中进行必要的检查,但这似乎更加类型安全(和我认为性能更高一些,因为所有检查都是在编译时完成的)。
10 个解决方案
#1
The closest you can do (except for your base-interface approach) is "where T : class
", meaning reference-type. There is no syntax to mean "any interface".
你可以做的最接近的(除了你的基接口方法)是“where T:class”,意思是引用类型。没有语法表示“任何接口”。
This ("where T : class
") is used, for example, in WCF to limit clients to service contracts (interfaces).
例如,在WCF中使用(“where T:class”)将客户端限制为服务契约(接口)。
#2
I know this is a bit late but for those that are interested you can use a runtime check.
我知道这有点晚了但对于那些感兴趣的人你可以使用运行时检查。
typeof(T).IsInterface
#3
No, actually, if you are thinking class
and struct
mean class
es and struct
s, you're wrong. class
means any reference type (e.g. includes interfaces too) and struct
means any value type (e.g. struct
, enum
).
不,实际上,如果你认为类和结构意味着类和结构,那你就错了。 class表示任何引用类型(例如也包括接口),struct表示任何值类型(例如struct,enum)。
#4
To follow up on Robert's answer, this is even later, but you can use a static helper class to make the runtime check once only per type:
为了跟进Robert的答案,这甚至是后来的,但您可以使用静态助手类来仅对每种类型进行一次运行时检查:
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() { /*...*/ }
}
I also note that your "should work" solution does not, in fact, work. Consider:
我还注意到,你的“应该工作”的解决方案实际上并没有起作用。考虑:
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; } }
Now there's nothing stopping you from calling Foo thus:
现在没有什么可以阻止你这样叫Foo:
Foo<Actual>();
The Actual
class, after all, satisfies the IBase
constraint.
毕竟,Actual类满足IBase约束。
#5
For some time now I've been thinking about near-compile-time constraints, so this is a perfect opportunity to launch the concept.
一段时间以来,我一直在考虑近编译时限制,所以这是启动这个概念的绝佳机会。
The basic idea is that if you cannot do a check compile time, you should do it at the earliest possible point in time, which is basically the moment the application starts. If all checks are okay, the application will run; if a check fails, the application will fail instantly.
基本的想法是,如果你不能做一个检查编译时间,你应该尽早完成,这基本上是应用程序启动的那一刻。如果所有检查都没问题,应用程序将运行;如果检查失败,应用程序将立即失败。
Behavior
The best possible outcome is that our program doesn't compile if the constraints are not met. Unfortunately that's not possible in the current C# implementation.
最好的结果是如果不满足约束,我们的程序就不会编译。不幸的是,这在当前的C#实现中是不可能的。
Next best thing is that the program crashes the moment it's started.
接下来最好的事情是程序在它启动的那一刻就崩溃了。
The last option is that the program will crash the moment the code is hit. This is the default behavior of .NET. For me, this is completely unacceptable.
最后一个选项是程序在代码被命中时崩溃。这是.NET的默认行为。对我来说,这是完全不可接受的。
Prerequirements
We need to have a constraint mechanism, so for the lack of anything better... let's use an attribute. The attribute will be present on top of a generic constraint to check if it matches our conditions. If it doesn't, we give an ugly error.
我们需要有一个约束机制,所以对于缺少更好的东西......让我们使用一个属性。该属性将出现在通用约束之上,以检查它是否与我们的条件匹配。如果没有,我们会给出一个丑陋的错误。
This enables us to do things like this in our code:
这使我们能够在代码中执行以下操作:
public class Clas<[IsInterface] T> where T : class
(I've kept the where T:class
here, because I always prefer compile-time checks to run-time checks)
(我在这里保留了T:class,因为我总是喜欢编译时检查到运行时检查)
So, that only leaves us with 1 problem, which is checking if all the types that we use match the constraint. How hard can it be?
所以,这只留给我们一个问题,即检查我们使用的所有类型是否与约束匹配。它能有多难?
Let's break it up
让我们分手吧
Generic types are always either on a class (/struct/interface) or on a method.
泛型类型总是在类(/ struct / interface)或方法上。
Triggering a constraint requires you to do one of the following things:
触发约束要求您执行以下操作之一:
- Compile-time, when using a type in a type (inheritance, generic constraint, class member)
- Compile-time, when using a type in a method body
- Run-time, when using reflection to construct something based on the generic base class.
- Run-time, when using reflection to construct something based on RTTI.
编译时,在类型中使用类型(继承,泛型约束,类成员)
编译时,在方法体中使用类型时
运行时,使用反射来构造基于泛型基类的东西。
运行时,使用反射构建基于RTTI的东西。
At this point, I would like to state that you should always avoid doing (4) in any program IMO. Regardless, these checks won't support it, since it would effectively mean solving the halting problem.
在这一点上,我想说你应该总是避免在任何IMO程序中做(4)。无论如何,这些检查都不会支持它,因为它实际上意味着解决停止问题。
Case 1: using a type
案例1:使用类型
Example:
public class TestClass : SomeClass<IMyInterface> { ... }
Example 2:
public class TestClass
{
SomeClass<IMyInterface> myMember; // or a property, method, etc.
}
Basically this involves scanning all types, inheritance, members, parameters, etc, etc, etc. If a type is a generic type and has a constraint, we check the constraint; if it's an array, we check the element type.
基本上这涉及扫描所有类型,继承,成员,参数等等。如果类型是泛型类型并且有约束,我们检查约束;如果它是一个数组,我们检查元素类型。
At this point I must add that this will break the fact that by default .NET loads types 'lazy'. By scanning all the types, we force the .NET runtime to load them all. For most programs this shouldn't be a problem; still, if you use static initializers in your code, you might encounter problems with this approach... That said, I wouldn't advice anyone to do this anyways (except for things like this :-), so it shouldn't give you a lot of problems.
在这一点上,我必须补充一点,这将打破这样一个事实,即默认情况下.NET加载类型'lazy'。通过扫描所有类型,我们强制.NET运行时加载它们。对于大多数程序来说,这应该不是问题;仍然,如果你在你的代码中使用静态初始化器,你可能会遇到这种方法的问题......那就是说,我不建议任何人这样做(除了这样的事情:-),所以它不应该给你有很多问题。
Case 2: using a type in a method
案例2:在方法中使用类型
Example:
void Test() {
new SomeClass<ISomeInterface>();
}
To check this we have only 1 option: decompile the class, check all member tokens that are used and if one of them is the generic type - check the arguments.
要检查这一点,我们只有一个选项:反编译该类,检查所有使用的成员标记,如果其中一个是泛型类型 - 检查参数。
Case 3: Reflection, runtime generic construction
案例3:反射,运行时通用构造
Example:
typeof(CtorTest<>).MakeGenericType(typeof(IMyInterface))
I suppose it's theoretically possible to check this with similar tricks as case (2), but the implementation of it is much harder (you need to check if MakeGenericType
is called in some code path). I won't go into details here...
我认为理论上可以使用与case(2)类似的技巧来检查它,但是它的实现要困难得多(你需要检查是否在某些代码路径中调用了MakeGenericType)。我不会在这里详述......
Case 4: Reflection, runtime RTTI
案例4:反射,运行时RTTI
Example:
Type t = Type.GetType("CtorTest`1[IMyInterface]");
This is the worst case scenario and as I explained before generally a bad idea IMHO. Either way, there's no practical way to figure this out using checks.
这是最糟糕的情况,正如我之前解释的那样,恕我直言。无论哪种方式,使用检查都没有实际的方法来解决这个问题。
Testing the lot
测试批次
Creating a program that tests case (1) and (2) will result in something like this:
创建一个测试case(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);
}
}
}
Using the code
使用代码
Well, that's the easy part :-)
嗯,这很容易:-)
// 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();
}
}
#6
You cannot do this in any released version of C#, nor in the upcoming C# 4.0. It's not a C# limitation, either - there's no "interface" constraint in the CLR itself.
您不能在任何已发布的C#版本中执行此操作,也不能在即将到来的C#4.0中执行此操作。它也不是C#限制 - 在CLR本身中没有“接口”约束。
#7
If possible, I went with a solution like this. It only works if you want several specific interfaces (e.g. those you have source access to) to be passed as a generic parameter, not any.
如果可能的话,我选择了这样的解决方案。它只适用于您希望将几个特定接口(例如,您具有源访问权限的接口)作为通用参数传递,而不是任何接口。
- I let my interfaces, which came into question, inherit an empty interface
IInterface
. - I constrained the generic T parameter to be of
IInterface
我让我的接口产生了问题,继承了一个空接口IInterface。
我将通用T参数约束为IInterface
In source, it looks like this:
在源代码中,它看起来像这样:
-
Any interface you want to be passed as the generic parameter:
您希望作为通用参数传递的任何接口:
public interface IWhatever : IInterface { // IWhatever specific declarations }
-
IInterface:
public interface IInterface { // Nothing in here, keep moving }
-
The class on which you want to put the type constraint:
要放置类型约束的类:
public class WorldPeaceGenerator<T> where T : IInterface { // Actual world peace generating code }
IInterface:公共接口IInterface { //这里什么都没有,继续前进 }
#8
What you have settled for is the best you can do:
您已满足的是您可以做的最好的事情:
public bool Foo<T>() where T : IBase;
#9
I tried to do something similar and used a workaround solution: I thought about implicit and explicit operator on structure: The idea is to wrap the Type in a structure that can be converted into Type implicitly.
我尝试做类似的事情并使用了一种解决方案:我考虑了结构上的隐式和显式运算符:我们的想法是将Type包装在一个可以隐式转换为Type的结构中。
Here is such a structure:
这是一个这样的结构:
public struct InterfaceType { private Type _type;
public struct InterfaceType {private Type _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));
}
}
basic usage:
// OK
InterfaceType type1 = typeof(System.ComponentModel.INotifyPropertyChanged);
// Throws an exception
InterfaceType type2 = typeof(WeakReference);
You have to imagine your own mecanism around this, but an example could be a method taken a InterfaceType in parameter instead of a type
你必须想象你自己的机制主义,但一个例子可能是在参数而不是类型中采用InterfaceType的方法
this.MyMethod(typeof(IMyType)) // works
this.MyMethod(typeof(MyType)) // throws exception
A method to override that should returns interface types:
覆盖它的方法应该返回接口类型:
public virtual IEnumerable<InterfaceType> GetInterfaces()
There are maybe things to do with generics also, but I didn't tried
也许与仿制药有关,但我没试过
Hope this can help or gives ideas :-)
希望这可以帮助或给出想法:-)
#10
Use an abstract class instead. So, you would have something like:
请改用抽象类。所以,你会有类似的东西:
public bool Foo<T>() where T : CBase;
#1
The closest you can do (except for your base-interface approach) is "where T : class
", meaning reference-type. There is no syntax to mean "any interface".
你可以做的最接近的(除了你的基接口方法)是“where T:class”,意思是引用类型。没有语法表示“任何接口”。
This ("where T : class
") is used, for example, in WCF to limit clients to service contracts (interfaces).
例如,在WCF中使用(“where T:class”)将客户端限制为服务契约(接口)。
#2
I know this is a bit late but for those that are interested you can use a runtime check.
我知道这有点晚了但对于那些感兴趣的人你可以使用运行时检查。
typeof(T).IsInterface
#3
No, actually, if you are thinking class
and struct
mean class
es and struct
s, you're wrong. class
means any reference type (e.g. includes interfaces too) and struct
means any value type (e.g. struct
, enum
).
不,实际上,如果你认为类和结构意味着类和结构,那你就错了。 class表示任何引用类型(例如也包括接口),struct表示任何值类型(例如struct,enum)。
#4
To follow up on Robert's answer, this is even later, but you can use a static helper class to make the runtime check once only per type:
为了跟进Robert的答案,这甚至是后来的,但您可以使用静态助手类来仅对每种类型进行一次运行时检查:
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() { /*...*/ }
}
I also note that your "should work" solution does not, in fact, work. Consider:
我还注意到,你的“应该工作”的解决方案实际上并没有起作用。考虑:
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; } }
Now there's nothing stopping you from calling Foo thus:
现在没有什么可以阻止你这样叫Foo:
Foo<Actual>();
The Actual
class, after all, satisfies the IBase
constraint.
毕竟,Actual类满足IBase约束。
#5
For some time now I've been thinking about near-compile-time constraints, so this is a perfect opportunity to launch the concept.
一段时间以来,我一直在考虑近编译时限制,所以这是启动这个概念的绝佳机会。
The basic idea is that if you cannot do a check compile time, you should do it at the earliest possible point in time, which is basically the moment the application starts. If all checks are okay, the application will run; if a check fails, the application will fail instantly.
基本的想法是,如果你不能做一个检查编译时间,你应该尽早完成,这基本上是应用程序启动的那一刻。如果所有检查都没问题,应用程序将运行;如果检查失败,应用程序将立即失败。
Behavior
The best possible outcome is that our program doesn't compile if the constraints are not met. Unfortunately that's not possible in the current C# implementation.
最好的结果是如果不满足约束,我们的程序就不会编译。不幸的是,这在当前的C#实现中是不可能的。
Next best thing is that the program crashes the moment it's started.
接下来最好的事情是程序在它启动的那一刻就崩溃了。
The last option is that the program will crash the moment the code is hit. This is the default behavior of .NET. For me, this is completely unacceptable.
最后一个选项是程序在代码被命中时崩溃。这是.NET的默认行为。对我来说,这是完全不可接受的。
Prerequirements
We need to have a constraint mechanism, so for the lack of anything better... let's use an attribute. The attribute will be present on top of a generic constraint to check if it matches our conditions. If it doesn't, we give an ugly error.
我们需要有一个约束机制,所以对于缺少更好的东西......让我们使用一个属性。该属性将出现在通用约束之上,以检查它是否与我们的条件匹配。如果没有,我们会给出一个丑陋的错误。
This enables us to do things like this in our code:
这使我们能够在代码中执行以下操作:
public class Clas<[IsInterface] T> where T : class
(I've kept the where T:class
here, because I always prefer compile-time checks to run-time checks)
(我在这里保留了T:class,因为我总是喜欢编译时检查到运行时检查)
So, that only leaves us with 1 problem, which is checking if all the types that we use match the constraint. How hard can it be?
所以,这只留给我们一个问题,即检查我们使用的所有类型是否与约束匹配。它能有多难?
Let's break it up
让我们分手吧
Generic types are always either on a class (/struct/interface) or on a method.
泛型类型总是在类(/ struct / interface)或方法上。
Triggering a constraint requires you to do one of the following things:
触发约束要求您执行以下操作之一:
- Compile-time, when using a type in a type (inheritance, generic constraint, class member)
- Compile-time, when using a type in a method body
- Run-time, when using reflection to construct something based on the generic base class.
- Run-time, when using reflection to construct something based on RTTI.
编译时,在类型中使用类型(继承,泛型约束,类成员)
编译时,在方法体中使用类型时
运行时,使用反射来构造基于泛型基类的东西。
运行时,使用反射构建基于RTTI的东西。
At this point, I would like to state that you should always avoid doing (4) in any program IMO. Regardless, these checks won't support it, since it would effectively mean solving the halting problem.
在这一点上,我想说你应该总是避免在任何IMO程序中做(4)。无论如何,这些检查都不会支持它,因为它实际上意味着解决停止问题。
Case 1: using a type
案例1:使用类型
Example:
public class TestClass : SomeClass<IMyInterface> { ... }
Example 2:
public class TestClass
{
SomeClass<IMyInterface> myMember; // or a property, method, etc.
}
Basically this involves scanning all types, inheritance, members, parameters, etc, etc, etc. If a type is a generic type and has a constraint, we check the constraint; if it's an array, we check the element type.
基本上这涉及扫描所有类型,继承,成员,参数等等。如果类型是泛型类型并且有约束,我们检查约束;如果它是一个数组,我们检查元素类型。
At this point I must add that this will break the fact that by default .NET loads types 'lazy'. By scanning all the types, we force the .NET runtime to load them all. For most programs this shouldn't be a problem; still, if you use static initializers in your code, you might encounter problems with this approach... That said, I wouldn't advice anyone to do this anyways (except for things like this :-), so it shouldn't give you a lot of problems.
在这一点上,我必须补充一点,这将打破这样一个事实,即默认情况下.NET加载类型'lazy'。通过扫描所有类型,我们强制.NET运行时加载它们。对于大多数程序来说,这应该不是问题;仍然,如果你在你的代码中使用静态初始化器,你可能会遇到这种方法的问题......那就是说,我不建议任何人这样做(除了这样的事情:-),所以它不应该给你有很多问题。
Case 2: using a type in a method
案例2:在方法中使用类型
Example:
void Test() {
new SomeClass<ISomeInterface>();
}
To check this we have only 1 option: decompile the class, check all member tokens that are used and if one of them is the generic type - check the arguments.
要检查这一点,我们只有一个选项:反编译该类,检查所有使用的成员标记,如果其中一个是泛型类型 - 检查参数。
Case 3: Reflection, runtime generic construction
案例3:反射,运行时通用构造
Example:
typeof(CtorTest<>).MakeGenericType(typeof(IMyInterface))
I suppose it's theoretically possible to check this with similar tricks as case (2), but the implementation of it is much harder (you need to check if MakeGenericType
is called in some code path). I won't go into details here...
我认为理论上可以使用与case(2)类似的技巧来检查它,但是它的实现要困难得多(你需要检查是否在某些代码路径中调用了MakeGenericType)。我不会在这里详述......
Case 4: Reflection, runtime RTTI
案例4:反射,运行时RTTI
Example:
Type t = Type.GetType("CtorTest`1[IMyInterface]");
This is the worst case scenario and as I explained before generally a bad idea IMHO. Either way, there's no practical way to figure this out using checks.
这是最糟糕的情况,正如我之前解释的那样,恕我直言。无论哪种方式,使用检查都没有实际的方法来解决这个问题。
Testing the lot
测试批次
Creating a program that tests case (1) and (2) will result in something like this:
创建一个测试case(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);
}
}
}
Using the code
使用代码
Well, that's the easy part :-)
嗯,这很容易:-)
// 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();
}
}
#6
You cannot do this in any released version of C#, nor in the upcoming C# 4.0. It's not a C# limitation, either - there's no "interface" constraint in the CLR itself.
您不能在任何已发布的C#版本中执行此操作,也不能在即将到来的C#4.0中执行此操作。它也不是C#限制 - 在CLR本身中没有“接口”约束。
#7
If possible, I went with a solution like this. It only works if you want several specific interfaces (e.g. those you have source access to) to be passed as a generic parameter, not any.
如果可能的话,我选择了这样的解决方案。它只适用于您希望将几个特定接口(例如,您具有源访问权限的接口)作为通用参数传递,而不是任何接口。
- I let my interfaces, which came into question, inherit an empty interface
IInterface
. - I constrained the generic T parameter to be of
IInterface
我让我的接口产生了问题,继承了一个空接口IInterface。
我将通用T参数约束为IInterface
In source, it looks like this:
在源代码中,它看起来像这样:
-
Any interface you want to be passed as the generic parameter:
您希望作为通用参数传递的任何接口:
public interface IWhatever : IInterface { // IWhatever specific declarations }
-
IInterface:
public interface IInterface { // Nothing in here, keep moving }
-
The class on which you want to put the type constraint:
要放置类型约束的类:
public class WorldPeaceGenerator<T> where T : IInterface { // Actual world peace generating code }
IInterface:公共接口IInterface { //这里什么都没有,继续前进 }
#8
What you have settled for is the best you can do:
您已满足的是您可以做的最好的事情:
public bool Foo<T>() where T : IBase;
#9
I tried to do something similar and used a workaround solution: I thought about implicit and explicit operator on structure: The idea is to wrap the Type in a structure that can be converted into Type implicitly.
我尝试做类似的事情并使用了一种解决方案:我考虑了结构上的隐式和显式运算符:我们的想法是将Type包装在一个可以隐式转换为Type的结构中。
Here is such a structure:
这是一个这样的结构:
public struct InterfaceType { private Type _type;
public struct InterfaceType {private Type _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));
}
}
basic usage:
// OK
InterfaceType type1 = typeof(System.ComponentModel.INotifyPropertyChanged);
// Throws an exception
InterfaceType type2 = typeof(WeakReference);
You have to imagine your own mecanism around this, but an example could be a method taken a InterfaceType in parameter instead of a type
你必须想象你自己的机制主义,但一个例子可能是在参数而不是类型中采用InterfaceType的方法
this.MyMethod(typeof(IMyType)) // works
this.MyMethod(typeof(MyType)) // throws exception
A method to override that should returns interface types:
覆盖它的方法应该返回接口类型:
public virtual IEnumerable<InterfaceType> GetInterfaces()
There are maybe things to do with generics also, but I didn't tried
也许与仿制药有关,但我没试过
Hope this can help or gives ideas :-)
希望这可以帮助或给出想法:-)
#10
Use an abstract class instead. So, you would have something like:
请改用抽象类。所以,你会有类似的东西:
public bool Foo<T>() where T : CBase;