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
).
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?
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).
IBase
— used in this way — are called marker interfaces. They enable special behaviours for 'marked' types.
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".
This ("where T : class
") is used, for example, in WCF to limit clients to service contracts (interfaces).
I know this is a bit late but for those that are interested you can use a runtime check.
typeof(T).IsInterface
Foo(Type type)
.
if (new T() is IMyInterface) { }
to check if an interface is implemented by the T class. Might not be the most efficient, but it works.
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
).
where T : struct
constraint.
class
, but declaring a storage location of an interface type really declares the storage location to be a class reference which implement that type.
where T : struct
corresponds to NotNullableValueTypeConstraint
, so it means it must be a value type other than Nullable<>
. (So Nullable<>
is a struct type which doesn't satisfy the where T : struct
constraint.)
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:
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<Actual>();
The Actual
class, after all, satisfies the IBase
constraint.
static
constructor can't be public
, so this should give a compile-time error. Also your static
class contains an instance method, that's a compile-time error as well.
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.
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.
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)
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.
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.
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.
Case 1: using a type
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.
Case 2: using a type in a method
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
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 4: Reflection, runtime 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:
[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();
}
}
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.
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
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
is not constrained to interfaces, it's constrained to anything that implements IInterface
- which any type can do if it wants to, e.g. struct Foo : IInterface
since your IInterface
is most likely public (otherwise everything that accepts it would have to be internal).
What you have settled for is the best you can do:
public bool Foo<T>() where T : IBase;
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.
Here is such a structure:
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
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 :-)
Solution A: This combination of constraints should guarantee that TInterface
is an interface:
class example<TInterface, TStruct>
where TStruct : struct, TInterface
where TInterface : class
{ }
It requires a single struct TStruct
as a Witness to proof that TInterface
is a struct.
You can use single struct as a witness for all your non-generic types:
struct InterfaceWitness : IA, IB, IC
{
public int DoA() => throw new InvalidOperationException();
//...
}
Solution B: If you don't want to make structs as witnesses you can create an interface
interface ISInterface<T>
where T : ISInterface<T>
{ }
and use a constraint:
class example<TInterface>
where TInterface : ISInterface<TInterface>
{ }
Implementation for interfaces:
interface IA :ISInterface<IA>{ }
This solves some of the problems, but requires trust that noone implements ISInterface<T>
for non-interface types, but that is pretty hard to do accidentally.
Use an abstract class instead. So, you would have something like:
public bool Foo<T>() where T : CBase;
Success story sharing
interface
constraint onT
should allow reference comparisons betweenT
and any other reference type, since reference comparisons are allowed between any interface and almost any other reference type, and allowing comparisons even that case would pose no problem.