【问题标题】:How can I use interface as a C# generic type constraint?如何将接口用作 C# 泛型类型约束?
【发布时间】:2010-11-08 22:55:05
【问题描述】:

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

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

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

目前我已经适应了:

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

其中 IBase 被定义为一个空接口,它被我的所有自定义接口继承...并不理想,但它应该可以工作...为什么不能定义泛型类型必须是接口?

对于它的价值,我想要这个,因为Foo 在需要接口类型的地方进行反射......我可以将它作为普通参数传入并在函数本身中进行必要的检查,但这似乎是更多类型安全(我想性能更高一点,因为所有检查都是在编译时完成的)。

【问题讨论】:

  • 实际上,您的 IBase dea 是迄今为止我见过的最好的。不幸的是,您不能将它用于您不拥有的接口。 C# 所要做的就是让所有接口都从 IOjbect 继承,就像所有类都从 Object 继承一样。
  • 注意:这恰好是一个相当普遍的想法。像IBase 这样的空接口——以这种方式使用——被称为标记接口。它们为“标记”类型启用特殊行为。
  • 其他注意事项:根据Interface design guidelines,应避免使用标记接口

标签: c# generics interface constraints


【解决方案1】:

不,实际上,如果您认为classstruct 的意思是classes 和structs,那您就错了。 class 表示任何引用类型(例如也包括接口),struct 表示任何值类型(例如structenum)。

【讨论】:

  • 这不是定义类和结构之间的区别吗:每个类都是引用类型(反之亦然),结构/值类型也是如此
  • Matthew:值类型比 C# 结构更多。例如,枚举是值类型并匹配 where T : struct 约束。
  • 值得注意的是,约束中使用的接口类型并不暗示class,但声明接口类型的存储位置实际上将存储位置声明为实现该类型的类引用。
  • 更准确地说,where T : struct 对应于NotNullableValueTypeConstraint,所以它必须是一个值类型other 而不是Nullable&lt;&gt;。 (所以Nullable&lt;&gt; 是一个不满足where T : struct 约束的结构类型。)
【解决方案2】:

您不能在任何已发布的 C# 版本中执行此操作,也不能在即将发布的 C# 4.0 中执行此操作。这也不是 C# 限制 - CLR 本身没有“接口”约束。

【讨论】:

    【解决方案3】:

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

    此 ("where T : class") 用于,例如,在 WCF 中将客户端限制为服务合同(接口)。

    【讨论】:

    • 不错的答案,但是您知道为什么这种语法不存在吗?看起来这将是一个不错的功能。
    • @StephenHolt:我认为 .NET 的创建者在决定允许哪些约束时,专注于那些可以让泛型类和方法使用泛型类型来完成原本无法做到的事情,而不是而不是防止它们以荒谬的方式使用。话虽如此,T 上的 interface 约束应该允许在 T 和任何其他引用类型之间进行引用比较,因为允许在任何接口和几乎任何其他引用类型之间进行引用比较,并且即使在这种情况下也允许比较没有问题。
    • @supercat 这种假设约束的另一个有用应用是为该类型的实例安全地创建代理。对于接口,它保证是安全的,而对于密封类,它会失败,与具有非虚拟方法的类相同。
    • @IvanDanilov:有许多可以想象的限制条件,如果允许的话,将有效地阻止一些无意义的结构。我同意对“任何接口类型”的约束会很好,但我认为它不会允许没有它就无法完成的任何事情,除了在尝试执行时生成编译时尖叫声否则可能在运行时失败的事情。
    【解决方案4】:

    改用抽象类。所以,你会有类似的东西:

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

    【讨论】:

    • 你不能总是用抽象类替换接口,因为 C# 不支持多重继承。
    【解决方案5】:

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

    typeof(T).IsInterface
    

    【讨论】:

    • +1 是指出这一点的唯一答案。我刚刚添加了一个答案,通过只检查每种类型而不是每次调用该方法来提高性能。
    • C# 中泛型的整个想法是具有编译时安全性。您的建议也可以使用非泛型方法Foo(Type type) 执行。
    • 我喜欢运行时检查。谢谢。
    • 也可以在运行时使用if (new T() is IMyInterface) { } 来检查接口是否由T 类实现。可能不是最有效的,但它确实有效。
    【解决方案6】:

    你已经决定的就是你能做的最好的事情:

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

    【讨论】:

      【解决方案7】:

      为了跟进 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() { /*...*/ }
      }
      

      我还注意到,您的“应该有效”的解决方案实际上不起作用。考虑:

      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 约束。

      【讨论】:

      • static 构造函数不能是public,所以这应该会产生编译时错误。此外,您的 static 类包含一个实例方法,这也是编译时错误。
      • 迟到感谢 nawfal 纠正@JeppeStigNielsen 指出的错误
      【解决方案8】:

      我尝试做类似的事情并使用了一种解决方法:我考虑了结构上的隐式和显式运算符:想法是将 Type 包装在可以隐式转换为 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));
      }
      

      }

      基本用法:

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

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

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

      一个应该返回接口类型的重写方法:

      public virtual IEnumerable<InterfaceType> GetInterfaces()
      

      也可能与泛型有关,但我没有尝试过

      希望这可以帮助或提供想法:-)

      【讨论】:

        【解决方案9】:

        如果可能,我会采用这样的解决方案。仅当您希望将几个特定接口(例如,您可以访问源代码的接口)作为通用参数而不是任何参数传递时,它才有效。

        • 我让我遇到问题的接口继承了一个空接口IInterface
        • 我将通用 T 参数限制为 IInterface

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

        • 您希望作为通用参数传递的任何接口:

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

          public interface IInterface
          {
              // Nothing in here, keep moving
          }
          
        • 您要对其进行类型约束的类:

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

        【讨论】:

        • 这并没有太大的作用。您的 T 不受接口限制,它受任何实现 IInterface 的限制 - any 类型可以根据需要执行,例如struct Foo : IInterface 因为您的 IInterface 很可能是公开的(否则接受它的所有内容都必须是内部的)。
        • 如果你控制了所有你想接受的类型,那么你可以使用代码生成来创建所有合适的重载,所有这些都只是重定向到一个通用的私有方法。
        【解决方案10】:

        一段时间以来,我一直在考虑接近编译时的约束,所以这是一个推出这个概念的绝佳机会。

        基本思想是,如果你不能做检查编译时间,你应该在尽可能早的时间点做,这基本上是应用程序启动的那一刻。如果所有检查都正常,则应用程序将运行;如果检查失败,应用程序将立即失败。

        行为

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

        接下来最好的事情是程序在启动的那一刻就崩溃了。

        最后一个选项是程序将在代码被击中的那一刻崩溃。这是 .NET 的默认行为。 对我来说,这是完全不能接受的。

        先决条件

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

        这使我们能够在我们的代码中做这样的事情:

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

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

        所以,这只给我们留下了 1 个问题,即检查我们使用的所有类型是否都匹配约束。有多难?

        我们分手吧

        泛型类型总是在类(/struct/interface)或方法上。

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

        1. 编译时,在类型中使用类型时(继承、泛型约束、类成员)
        2. 编译时,在方法体中使用类型时
        3. 运行时,当使用反射构造基于通用基类的东西时。
        4. 运行时,当使用反射构造基于 RTTI 的东西时。

        在这一点上,我想声明您应该始终避免在任何程序 IMO 中执行 (4)。无论如何,这些检查不会支持它,因为它实际上意味着解决停机问题。

        案例 1:使用类型

        例子:

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

        示例 2:

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

        基本上这涉及扫描所有类型、继承、成员、参数等。如果一个类型是泛型类型并且有约束,我们检查约束;如果是数组,我们检查元素类型。

        此时我必须补充一点,这将打破默认情况下 .NET 加载类型“惰性”的事实。通过扫描所有类型,我们强制 .NET 运行时加载它们。对于大多数程序来说,这应该不是问题。尽管如此,如果您在代码中使用静态初始化程序,您可能会遇到这种方法的问题......也就是说,我不会建议任何人这样做(除了这样的事情:-),所以它不应该给出你有很多问题。

        案例 2:在方法中使用类型

        例子:

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

        要检查这一点,我们只有 1 个选项:反编译类,检查所有使用的成员标记,如果其中一个是泛型​​类型 - 检查参数。

        案例 3:反射,运行时泛型构造

        例子:

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

        我想理论上可以使用与案例 (2) 类似的技巧来检查它,但它的实现要困难得多(您需要检查是否在某些代码路径中调用了 MakeGenericType)。此处不赘述……

        案例 4:反射,运行时 RTTI

        例子:

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

        这是最坏的情况,正如我之前解释的那样,恕我直言,这通常是个坏主意。无论哪种方式,都没有实用的方法来使用检查来解决这个问题。

        测试批次

        创建一个测试用例 (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();
            }
        }
        

        【讨论】:

        • 您甚至可以在单元测试中将其移至应用程序启动之前,而不是静态的 on-app-start,这与运行时稍有不同。
        【解决方案11】:

        解决方案 A: 这种约束组合应该保证TInterface 是一个接口:

        class example<TInterface, TStruct>
            where TStruct : struct, TInterface
            where TInterface : class
        { }
        

        它需要一个结构体TStruct 作为见证人来证明TInterface 是一个结构体。

        您可以使用单个结构作为所有非泛型类型的见证:

        struct InterfaceWitness : IA, IB, IC 
        {
            public int DoA() => throw new InvalidOperationException();
            //...
        }
        

        解决方案 B: 如果你不想让结构体作为见证,你可以创建一个接口

        interface ISInterface<T>
            where T : ISInterface<T>
        { }
        

        并使用约束:

        class example<TInterface>
            where TInterface : ISInterface<TInterface>
        { }
        

        接口的实现:

        interface IA :ISInterface<IA>{ }
        

        这解决了一些问题,但需要相信没有人为非接口类型实现ISInterface&lt;T&gt;,但这很难意外做到。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-09-16
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多