Есть ли способ получить следующее объявление функции?
public bool Foo<T>() where T : interface;
то есть. где T - тип интерфейса (аналогично where T : class
, и struct
).
В настоящее время я согласился на:
public bool Foo<T>() where T : IBase;
Где IBase определяется как пустой интерфейс, который наследуется всеми моими пользовательскими интерфейсами ... Не идеально, но он должен работать ... Почему вы не можете определить, что универсальный тип должен быть интерфейсом?
Что бы это ни стоило, я хочу этого, потому что Foo
делаю отражение там, где нужен тип интерфейса ... Я мог бы передать его как обычный параметр и выполнить необходимую проверку в самой функции, но это казалось намного более безопасным (и я предположим, немного более производительный, так как все проверки выполняются во время компиляции).
c#
generics
interface
constraints
Мэтью Шарли
источник
источник
IBase
- используемые таким образом, называются интерфейсами маркеров . Они обеспечивают специальное поведение для «помеченных» типов.Ответы:
Самое близкое, что вы можете сделать (за исключением вашего подхода с базовым интерфейсом), это "
where T : class
", что означает ссылочный тип. Нет синтаксиса для обозначения «любой интерфейс».Это ("
where T : class
") используется, например, в WCF для ограничения клиентов контрактами на обслуживание (интерфейсами).источник
interface
ограничениеT
должно позволять сравнения ссылок междуT
любым другим типом ссылок, так как сравнения ссылок допускается между любым интерфейсом и почти любым другим типом ссылок, и разрешение сравнений даже в этом случае не представляет проблемы.Я знаю, что это немного поздно, но для тех, кто заинтересован, вы можете использовать проверку во время выполнения.
источник
Foo(Type type)
.if (new T() is IMyInterface) { }
чтобы проверить, реализован ли интерфейс классом T. Может быть не самым эффективным, но это работает.Нет, вообще-то, если вы думаете
class
иstruct
имеете ввидуclass
es иstruct
s, вы ошибаетесь.class
означает любой ссылочный тип (например, включает в себя также интерфейсы) иstruct
означает любой тип значения (напримерstruct
,enum
).источник
where T : struct
ограничением соответствия.class
, но объявление места хранения типа интерфейса действительно объявляет место хранения как ссылку на класс, реализующий этот тип.where T : struct
соответствуетNotNullableValueTypeConstraint
, значит, это должен быть тип значения, отличный отNullable<>
. (Nullable<>
Таков тип структуры, который не удовлетворяетwhere T : struct
ограничению.)Чтобы проконтролировать ответ Роберта, это еще позже, но вы можете использовать статический вспомогательный класс для проверки во время выполнения только один раз для каждого типа:
Я также отмечаю, что ваше решение «должно работать» на самом деле не работает. Рассматривать:
Теперь ничто не мешает вам называть Foo таким образом:
В
Actual
конце концов, класс удовлетворяетIBase
ограничению.источник
static
Конструктор не может бытьpublic
, так что это должно дать ошибку во время компиляции. Также вашstatic
класс содержит метод экземпляра, что также является ошибкой во время компиляции.В течение некоторого времени я думал об ограничениях во время компиляции, так что это прекрасная возможность запустить концепцию.
Основная идея заключается в том, что если вы не можете выполнить проверку времени компиляции, вы должны сделать это в кратчайшие сроки, то есть в основном с момента запуска приложения. Если все проверки в порядке, приложение будет запущено; если проверка не пройдена, приложение мгновенно завершится неудачей.
Поведение
Лучший возможный результат - наша программа не компилируется, если ограничения не выполнены. К сожалению, это невозможно в текущей реализации C #.
Следующим лучшим моментом является то, что программа вылетает в момент запуска.
Последний вариант заключается в том, что программа будет аварийно завершать работу при нажатии на код. Это поведение по умолчанию .NET. Для меня это совершенно неприемлемо.
Предварительно требования
Нам нужен механизм ограничений, поэтому из-за отсутствия чего-то лучшего ... давайте использовать атрибут. Атрибут будет присутствовать поверх общего ограничения, чтобы проверить, соответствует ли оно нашим условиям. Если этого не произойдет, мы выдаем ужасную ошибку.
Это позволяет нам делать такие вещи в нашем коде:
(Я сохранил
where T:class
здесь, потому что я всегда предпочитаю проверки во время компиляции проверкам во время выполнения)Таким образом, это оставляет только одну проблему, которая проверяет, соответствуют ли все типы, которые мы используем, ограничению. Как трудно это может быть?
Давайте разберемся
Универсальные типы всегда находятся в классе (/ struct / interface) или в методе.
Для запуска ограничения необходимо выполнить одно из следующих действий:
На этом этапе я хотел бы заявить, что вы всегда должны избегать выполнения (4) в любой программе IMO. Несмотря на это, эти проверки не поддержат его, поскольку это фактически означает решение проблемы остановки.
Случай 1: использование типа
Пример:
Пример 2:
В основном это включает в себя сканирование всех типов, наследования, членов, параметров и т. Д. И т. Д. И т. Д. Если тип является универсальным типом и имеет ограничение, мы проверяем ограничение; если это массив, мы проверяем тип элемента.
На данный момент я должен добавить, что это нарушит тот факт, что по умолчанию .NET загружает типы «ленивый». Сканируя все типы, мы заставляем среду .NET загружать их все. Для большинства программ это не должно быть проблемой; тем не менее, если вы используете статические инициализаторы в своем коде, вы можете столкнуться с проблемами при таком подходе ... Тем не менее, я бы никому не советовал делать это в любом случае (кроме таких вещей :-), поэтому он не должен давать У тебя много проблем.
Случай 2: использование типа в методе
Пример:
Чтобы проверить это, у нас есть только один вариант: декомпилировать класс, проверить все используемые токены-члены и, если один из них является универсальным типом, проверить аргументы.
Случай 3: Отражение, универсальная конструкция во время выполнения
Пример:
Я предполагаю, что теоретически возможно проверить это с помощью тех же приемов, что и в случае (2), но его реализация намного сложнее (вам нужно проверить,
MakeGenericType
вызывается ли в каком-то пути кода). Я не буду вдаваться в подробности здесь ...Случай 4: Отражение, время выполнения RTTI
Пример:
Это наихудший вариант развития событий, и, как я уже объяснял, вообще плохая идея, ИМХО. В любом случае, нет практического способа выяснить это с помощью чеков.
Тестирование лота
Создание программы, которая проверяет варианты (1) и (2), приведет к чему-то вроде этого:
Используя код
Ну, это легкая часть :-)
источник
Вы не можете сделать это ни в одной выпущенной версии C #, ни в следующей C # 4.0. Это также не ограничение C # - в самой CLR нет ограничения «интерфейса».
источник
Если возможно, я пошел с решением, как это. Это работает только в том случае, если вы хотите, чтобы несколько определенных интерфейсов (например, тех, к которым у вас есть доступ к источнику) были переданы в качестве общего параметра, а не любого.
IInterface
.IInterface
В источнике это выглядит так:
Любой интерфейс, который вы хотите передать как универсальный параметр:
IInterface:
Класс, в который вы хотите поместить ограничение типа:
источник
T
не ограничен интерфейсами, он ограничен всем, что реализуетIInterface
- что может делать любой тип, если он этого хочет, например,struct Foo : IInterface
поскольку вашIInterface
скорее всего общедоступен (в противном случае все, что его принимает, должно быть внутренним).То, что вы решили, это лучшее, что вы можете сделать:
источник
Я попытался сделать что-то подобное и использовал обходное решение: я подумал о неявном и явном операторе структуры: идея заключается в том, чтобы обернуть тип в структуру, которая может быть неявно преобразована в тип.
Вот такая структура:
публичная структура InterfaceType {приватный тип _type;
}
основное использование:
Вы должны представить свой собственный механизм вокруг этого, но примером может быть метод, принимающий InterfaceType в параметре вместо типа
Метод для переопределения, который должен возвращать типы интерфейса:
Возможно, есть и дела с дженериками, но я не пробовал
Надеюсь, что это может помочь или дает идеи :-)
источник
Решение A: Эта комбинация ограничений должна гарантировать, что
TInterface
это интерфейс:Требуется одна структура
TStruct
в качестве Свидетеля, чтобы доказать, чтоTInterface
это структура.Вы можете использовать одну структуру в качестве свидетеля для всех ваших неуниверсальных типов:
Решение B: Если вы не хотите создавать структуры в качестве свидетелей, вы можете создать интерфейс
и использовать ограничение:
Реализация для интерфейсов:
Это решает некоторые из проблем, но требует доверия, которое никто не реализует
ISInterface<T>
для неинтерфейсных типов, но это довольно сложно сделать случайно.источник
Вместо этого используйте абстрактный класс. Итак, у вас будет что-то вроде:
источник