インターフェイスをC#ジェネリック型制約として使用するにはどうすればよいですか?


164

次の関数宣言を取得する方法はありますか?

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

すなわち。ここで、Tはインターフェイスタイプです(where T : class、およびと同様struct)。

現在、私は解決しました:

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

IBaseが、すべてのカスタムインターフェイスによって継承される空のインターフェイスとして定義されている場合...理想的ではありませんが、機能するはずです...ジェネリック型をインターフェイスにする必要があると定義できないのはなぜですか?

価値Fooがあるのは、インターフェース型が必要な場所でリフレクションを行うためです。通常のパラメーターとして渡して、関数自体で必要なチェックを行うことができますが、これははるかに型保証されているように見えました(そして私はすべてのチェックがコンパイル時に行われるため、もう少しパフォーマンスが高いと仮定します)。


4
実際、あなたのIBase deaは私が今まで見た中で最高です。残念ながら、自分が所有していないインターフェイスには使用できません。すべてのクラスがObjectから継承するのと同じように、C#が実行する必要があるのは、すべてのインターフェイスがIOjbectから継承することです。
Rhyous

1
注:これはかなり一般的な考え方です。IBaseこのように使用される空のインターフェイスは、マーカーインターフェイスと呼ばれます。「マークされた」タイプの特別な動作を可能にします。
ピウス

回答:


132

あなたができる最も近い(あなたのベースインターフェースアプローチを除いて)は「where T : class、参照タイプを意味する "です。「任意のインターフェース」を意味する構文はありません。

これ( " where T : class")は、たとえば、クライアントをサービスコントラクト(インターフェイス)に制限するためにWCFで使用されます。


7
いい答えですが、なぜこの構文が存在しないのかわかりますか?これは便利な機能のようです。
スティーブンホルト

@StephenHolt:.NETの作成者は、どの制約を許可するかを決定する際に、ジェネリッククラスとメソッドに、ジェネリック型で使用できないようにすることではなく、ジェネリック型で使用できないものに焦点を当てたと思います無意味な方法。とは言っても、どのインターフェースと他のほとんどすべての参照型の間で参照比較が許可され、そのような場合でも比較を許可しても問題は生じないので、interface上の制約TTと他の参照型の間の参照比較を許可する必要があります。
スーパーキャット2014年

1
@supercatのような架空の制約のもう1つの有用なアプリケーションは、タイプのインスタンスのプロキシを安全に作成することです。インターフェースの場合は安全であることが保証されますが、シールされたクラスの場合は、非仮想メソッドを持つクラスの場合と同様に失敗します。
Ivan Danilov

@IvanDanilov:可能であれば、いくつかの無意味な構造を効果的にブロックする考えられる制約がいくつかあります。「任意のインターフェイスタイプ」の制約は良いことだと私は同意しますが、それなしでは実行できなかったすべてのことが可能になるとは思わないので、実行しようとしたときにコンパイル時のスコークが生成されることはありません。そうでなければ実行時に失敗する可能性があるもの。
スーパーキャット2017

113

私はこれが少し遅いことを知っていますが、興味がある人はランタイムチェックを使用できます。

typeof(T).IsInterface

11
これを指摘する唯一の答えである+1。メソッドが呼び出されるたびではなく、各タイプを1回だけチェックすることでパフォーマンスを向上させるアプローチを含む回答を追加しました。
phoog

9
C#でのジェネリックの全体的な考え方は、コンパイル時の安全性を確保することです。あなたが提案していることは、一般的ではない方法でも実行できますFoo(Type type)
JacekGorgoń14年

ランタイムチェックが好きです。ありがとう。
–TarıkÖzgünGüner2015

また、実行時にを使用if (new T() is IMyInterface) { }して、インターフェイスがTクラスによって実装されているかどうかを確認できます。最も効率的ではないかもしれませんが、動作します。
tkerwood 2017

26

いいえ、実際には、あなたが考えclassていてesとs をstruct意味するのであれば、あなたは間違っています。任意の参照タイプを意味し(たとえば、インターフェースも含む)、任意の値タイプを意味しますclassstructclassstruct(たとえばstructenum)。


1
ただし、クラスと構造体の違いの定義ではありません。すべてのクラスは参照型(およびその逆)であり、構造体/値型の
ディット

マシュー:値の型には、C#の構造体よりも多くのものがあります。たとえば、列挙型は値の型と一致where T : struct制約です。
Mehrdad Afshari、

制約で使用されるインターフェイスタイプはを意味しないことclassに注意してください。ただし、インターフェイスタイプの格納場所を宣言すると、その場所を実際にそのタイプを実装するクラス参照として宣言できます。
スーパーキャット2012年

4
、さらにより正確にwhere T : structに相当NotNullableValueTypeConstraintするので、それは値型でなければならないことを意味他のよりNullable<>。(つまりNullable<>where T : struct制約を満たさない構造体型も同様です。)
Jeppe Stig Nielsen '19

19

Robertの答えをフォローアップするために、これはさらに遅いですが、静的ヘルパークラスを使用して、型ごとに1回だけランタイムチェックを行うことができます。

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クラスにはインスタンスメソッドが含まれていますが、これもコンパイル時のエラーです。
Jeppe Stig Nielsen

@JeppeStigNielsenによって指摘されたエラーを修正してくださったnawfalのおかげで遅れました
phoog

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つのオプションしかありません。クラスを逆コンパイルし、使用されているすべてのメンバートークンをチェックし、そのうちの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();
    }
}

8

これは、リリースされたバージョンのC#でも、今後のC#4.0でも実行できません。これもC#の制限ではありません。CLR自体に「インターフェース」制約はありません。


6

可能であれば、私はこのような解決策を講じました。これは、いくつかの特定のインターフェース(例えば、ソースへのアクセス権を持つインターフェース)を、汎用パラメーターとしてではなく、汎用パラメーターとして渡したい場合にのみ機能します。

  • 問題となったインターフェースに空のインターフェースを継承させましたIInterface
  • ジェネリックTパラメーターを IInterface

ソースでは、次のようになります。

  • ジェネリックパラメーターとして渡されるインターフェイス:

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

    public interface IInterface
    {
        // Nothing in here, keep moving
    }
  • タイプ制約を設定するクラス:

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

これはあまり効果がありません。あなたTはインターフェースに制約されていません、それは実装するものに制約されていますIInterface- たとえば、あなたが望んでいるならどんなタイプでそれを行うことができます、例えばstruct Foo : IInterfaceあなたIInterfaceはおそらくパブリックです(そうでなければそれを受け入れるすべては内部でなければならないでしょう)。
AnorZaken

とにかく受け入れたいすべてのタイプを制御する場合は、コード生成を使用して、適切なすべてのオーバーロードを作成できます。これらのオーバーロードはすべて、一般的なプライベートメソッドにリダイレクトされるだけです。
AnorZaken

2

あなたが解決したのはあなたができる最善のことです:

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

2

私は同様のことを試みて回避策の解決策を使用しました:構造の暗黙的および明示的演算子について考えました:暗黙的に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));
}

}

基本的な使い方:

// OK
InterfaceType type1 = typeof(System.ComponentModel.INotifyPropertyChanged);

// Throws an exception
InterfaceType type2 = typeof(WeakReference);

あなたはこれについてあなた自身のメカニズムを想像する必要がありますが、例はタイプの代わりにパラメータでInterfaceTypeをとるメソッドであるかもしれません

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

インターフェースタイプを返す必要があるオーバーライドメソッド:

public virtual IEnumerable<InterfaceType> GetInterfaces()

ジェネリックにも関係があるかもしれませんが、私は試していません

これが役立つか、アイデアを与えることを願っています:-)


0

ソリューション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<T>非インターフェイス型には誰も実装しないという信頼を必要としますが、それを誤って行うのはかなり困難です。


-4

代わりに抽象クラスを使用してください。したがって、次のようなものになります。

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

10
C#は多重継承をサポートしていないため、インターフェイスを常に抽象クラスに置き換えることはできません。
Sam
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.