Як я можу використовувати інтерфейс як загальне обмеження типу 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 - це найкраще, що я бачив досі. На жаль, ви не можете використовувати його для інтерфейсів, якими ви не володієте. Все, що потрібно зробити C #, це зробити, щоб усі інтерфейси успадковувались від IOjbect, як і всі класи, успадковані від Object.
Rhyous

1
Примітка. Це трапляється досить поширеною ідеєю. Порожні інтерфейси на зразок IBase- використовуються таким чином - називаються маркерами інтерфейсів . Вони забезпечують особливу поведінку для 'позначених' типів.
пій

Відповіді:


132

Найближче, що ви можете зробити (крім вашого базового інтерфейсу) - це " where T : class", тобто тип опорного типу. Немає синтаксису, який би означав "будь-який інтерфейс".

Це (" where T : class") використовується, наприклад, у WCF для обмеження клієнтів на контракти на послуги (інтерфейси).


7
приємна відповідь, але чи маєте ви уявлення, чому цього синтаксису не існує? Здається, це було б приємною функцією.
Стівен Холт

@StephenHolt: Я думаю, що творці .NET, вирішуючи, які обмеження дозволити, були зосереджені на тих, які дозволяли б загальним класам та методам робити речі із загальними типами, які вони інакше не могли, а не на тому, щоб запобігти їх використанню в безглузді шляхи. interfaceЗважаючи на це, обмеження щодо цього Tповинно дозволяти порівнювати посилання між Tбудь-яким іншим типом посилань, оскільки порівняння посилань дозволено між будь-яким інтерфейсом і майже будь-яким іншим типом посилань, а допускати порівняння навіть у цьому випадку не складе жодних проблем.
supercat

1
@supercat Іншим корисним застосуванням такого гіпотетичного обмеження було б безпечне створення проксі для екземплярів типу. Для інтерфейсу це гарантовано є безпечним, тоді як для герметичних класів воно буде відмовити, як і для класів з невіртуальними методами.
Іван Данилов

@IvanDanilov: Існує ряд можливих обмежень, які, якщо це дозволять, корисно блокують деякі безглузді конструкції. Я погоджуюся, що обмеження для "будь-якого типу інтерфейсу" було б непогано, але я не бачу, що це дозволило б зробити все, що без нього не обійтися, за винятком покоління часу компіляції при спробах зробити. речі, які в іншому випадку можуть вийти з ладу під час виконання.
supercat

113

Я знаю, що це трохи пізно, але для тих, хто цікавиться, ви можете перевірити час виконання.

typeof(T).IsInterface

11
+1 за те, що є єдиною відповіддю на це. Я просто додав відповідь із підходом до підвищення продуктивності, перевіряючи кожен тип лише один раз, а не щоразу, коли метод викликається.
фог

9
Вся ідея генерики в C # полягає в безпеці для збирання часу. Те, що ви пропонуєте, так само добре може бути виконане негенеріальним методом Foo(Type type).
Яцек Горгонь

Мені подобається перевірка виконання. Дякую.
Tarık Özgün Güner

Також під час виконання ви можете if (new T() is IMyInterface) { }перевірити, чи інтерфейс реалізований класом T. Може бути не найефективнішим, але це працює.
tkerwood

26

Ні, насправді, якщо ви думаєте classі structмаєте на увазі classес і structс, ви помиляєтесь. classозначає будь-який тип посилань (наприклад, включає також інтерфейси) і structозначає будь-який тип значення (наприклад struct, enum).


1
Хіба це не визначення різниці між класом і структурою, хоч: що кожен клас є еталонним типом (і навпаки) і ditto для типів stuct / value
Matthew Scharley

Метью: Типи значення більше, ніж структури C #. Наприклад, переліки - це типи значень і where T : structобмеження відповідності .
Мехрдад Афшарі

Варто зазначити, що типи інтерфейсу, використовувані в обмеженнях, не мають на увазі class, але оголошення місця зберігання типу інтерфейсу дійсно оголошує місце зберігання сховищем класу, який реалізує цей тип.
supercat

4
Якщо бути ще більш точним, where T : structвідповідає NotNullableValueTypeConstraint, так що це означає, що він повинен бути типом значення, відмінним від Nullable<>. (Так Nullable<>це тип структури, яка не задовольняє where T : structобмеження.)
Jeppe Stig Nielsen

19

Щоб продовжити відповідь на Роберта, це ще пізніше, але ви можете використовувати статичний клас помічників, щоб зробити перевірку часу виконання лише один раз на тип:

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

Запізнілий спасибі nawfal за виправлення помилок, відмічених @JeppeStigNielsen
phoog

10

Деякий час я замислювався над обмеженнями, що складаються майже під час збирання, тому це прекрасна можливість запустити концепцію.

Основна ідея полягає в тому, що якщо ви не можете виконати час складання перевірки, ви повинні зробити це в найкоротші терміни, в основному з моменту запуску програми. Якщо всі перевірки в порядку, програма запуститься; якщо перевірка не вдасться, програма миттєво вийде з ладу.

Поведінка

Найкращий можливий результат полягає в тому, що наша програма не збирається, якщо обмеження не дотримані. На жаль, це неможливо в поточній реалізації C #.

Наступним найкращим є те, що програма виходить з ладу в момент запуску.

Останній варіант полягає в тому, що програма вийде з ладу в момент потрапляння коду. Це поведінка .NET за замовчуванням. Для мене це абсолютно неприпустимо.

Передумови

Потрібно мати механізм обмеження, тому для відсутності нічого кращого ... давайте скористаємося атрибутом. Атрибут буде присутній поверх загального обмеження, щоб перевірити, чи відповідає він нашим умовам. Якщо цього не відбувається, ми робимо некрасиву помилку.

Це дозволяє нам робити такі речі в нашому коді:

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

(Я зберігав where T:classтут, тому що я завжди вважаю за краще перевірки часу компіляції, ніж перевірки часу)

Отже, це залишає нам лише одну проблему, яка перевіряє, чи всі типи, які ми використовуємо, відповідають обмеженню. Як важко це може бути?

Давайте розбимо його

Загальні типи завжди є або на класі (/ структура / інтерфейс), або на методі.

Ініціювання обмеження вимагає зробити одну з таких дій:

  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]");

Це найгірший сценарій, і, як я пояснював, загалом погана ідея IMHO. Так чи інакше, немає практичного способу розібратися в цьому за допомогою чеків.

Тестування партії

Створення програми, яка тестує випадки (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
    }
  • Інтерфейс:

    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 structure 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 # не підтримує множинне успадкування.
Сем
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.