Как я могу использовать интерфейс как ограничение общего типа 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 - лучшее, что я видел до сих пор. К сожалению, вы не можете использовать его для интерфейсов, которые вам не принадлежат. Все, что нужно было бы сделать в C # - это иметь все интерфейсы, наследуемые от IOjbect, как и все классы, наследуемые от Object.
Rhyous
1
Примечание: это довольно распространенная идея. Пустые интерфейсы, такие как IBase- используемые таким образом, называются интерфейсами маркеров . Они обеспечивают специальное поведение для «помеченных» типов.
Пий

Ответы:

132

Самое близкое, что вы можете сделать (за исключением вашего подхода с базовым интерфейсом), это " where T : class", что означает ссылочный тип. Нет синтаксиса для обозначения «любой интерфейс».

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

Марк Гравелл
источник
7
хороший ответ, но у вас есть идея, почему этот синтаксис не существует? Похоже, это было бы приятно иметь функцию.
Стивен Холт
@StephenHolt: Я думаю, что создатели .NET, решая, какие ограничения разрешить, были сосредоточены на тех, которые позволили бы универсальным классам и методам делать вещи с универсальными типами, которые они иначе не могли бы, вместо того, чтобы препятствовать их использованию в бессмысленные способы. При этом interfaceограничение Tдолжно позволять сравнения ссылок между Tлюбым другим типом ссылок, так как сравнения ссылок допускается между любым интерфейсом и почти любым другим типом ссылок, и разрешение сравнений даже в этом случае не представляет проблемы.
суперкат
1
@supercat еще одно полезное применение такого гипотетического ограничения - безопасное создание прокси для экземпляров типа. Для интерфейса он гарантированно безопасен, в то время как для закрытых классов он потерпит неудачу, как и для классов с не виртуальными методами.
Иван Данилов
@IvanDanilov: Есть ряд возможных ограничений, которые, если позволят, будут полезны для блокирования некоторых бессмысленных конструкций. Я согласен, что ограничение для «любого типа интерфейса» было бы неплохо, но я не вижу, чтобы оно позволяло делать что-либо, что нельзя было бы сделать без него, за исключением генерации криков во время компиляции, когда предпринимаются попытки сделать это. вещи, которые иначе могли бы потерпеть неудачу во время выполнения.
суперкат
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имеете ввиду classes и structs, вы ошибаетесь. classозначает любой ссылочный тип (например, включает в себя также интерфейсы) и structозначает любой тип значения (например struct, enum).

Мехрдад Афшари
источник
1
Разве это не определение различия между классом и структурой, хотя: каждый класс является ссылочным типом (и наоборот) и тем же типом для типов stuct / value
Мэтью Шарли,
Мэтью: Есть больше, чтобы ценить типы, чем структуры C #. Например, перечисления являются типами значений и where T : structограничением соответствия.
Мехрдад Афшари
Стоит отметить, что типы интерфейса, используемые в ограничениях, не подразумевают class, но объявление места хранения типа интерфейса действительно объявляет место хранения как ссылку на класс, реализующий этот тип.
суперкат
4
Чтобы быть еще более точным, where T : structсоответствует NotNullableValueTypeConstraint, значит, это должен быть тип значения, отличный от Nullable<>. ( Nullable<>Таков тип структуры, который не удовлетворяет where T : structограничению.)
Джепп Стиг Нильсен
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ограничению.

phoog
источник
staticКонструктор не может быть public, так что это должно дать ошибку во время компиляции. Также ваш staticкласс содержит метод экземпляра, что также является ошибкой во время компиляции.
Джеппе Стиг Нильсен
Запоздалое спасибо nawfal за исправление ошибок, отмеченных @JeppeStigNielsen
phoog
10

В течение некоторого времени я думал об ограничениях во время компиляции, так что это прекрасная возможность запустить концепцию.

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

Поведение

Лучший возможный результат - наша программа не компилируется, если ограничения не выполнены. К сожалению, это невозможно в текущей реализации C #.

Следующим лучшим моментом является то, что программа вылетает в момент запуска.

Последний вариант заключается в том, что программа будет аварийно завершать работу при нажатии на код. Это поведение по умолчанию .NET. Для меня это совершенно неприемлемо.

Предварительно требования

Нам нужен механизм ограничений, поэтому из-за отсутствия чего-то лучшего ... давайте использовать атрибут. Атрибут будет присутствовать поверх общего ограничения, чтобы проверить, соответствует ли оно нашим условиям. Если этого не произойдет, мы выдаем ужасную ошибку.

Это позволяет нам делать такие вещи в нашем коде:

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

(Я сохранил where T:classздесь, потому что я всегда предпочитаю проверки во время компиляции проверкам во время выполнения)

Таким образом, это оставляет только одну проблему, которая проверяет, соответствуют ли все типы, которые мы используем, ограничению. Как трудно это может быть?

Давайте разберемся

Универсальные типы всегда находятся в классе (/ struct / interface) или в методе.

Для запуска ограничения необходимо выполнить одно из следующих действий:

  1. Время компиляции при использовании типа в типе (наследование, общее ограничение, член класса)
  2. Время компиляции при использовании типа в теле метода
  3. Время выполнения, когда используется отражение для создания чего-либо на основе базового базового класса.
  4. Время выполнения, при использовании отражения для построения чего-либо на основе RTTI.

На этом этапе я хотел бы заявить, что вы всегда должны избегать выполнения (4) в любой программе IMO. Несмотря на это, эти проверки не поддержат его, поскольку это фактически означает решение проблемы остановки.

Случай 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>();
}

Чтобы проверить это, у нас есть только один вариант: декомпилировать класс, проверить все используемые токены-члены и, если один из них является универсальным типом, проверить аргументы.

Случай 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();
    }
}
atlaste
источник
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;
KevinDeus
источник
2

Я попытался сделать что-то подобное и использовал обходное решение: я подумал о неявном и явном операторе структуры: идея заключается в том, чтобы обернуть тип в структуру, которая может быть неявно преобразована в тип.

Вот такая структура:

публичная структура InterfaceType {приватный тип _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>для неинтерфейсных типов, но это довольно сложно сделать случайно.

user13909106
источник
-4

Вместо этого используйте абстрактный класс. Итак, у вас будет что-то вроде:

public bool Foo<T>() where T : CBase;
Эдди
источник
10
Вы не всегда можете заменить интерфейс на абстрактный класс, так как C # не поддерживает множественное наследование.
Сэм