В этом вопросе происходит несколько вещей ...
Структура может реализовать интерфейс, но есть проблемы, связанные с приведением типов, изменчивостью и производительностью. См. Этот пост для более подробной информации: https://docs.microsoft.com/en-us/archive/blogs/abhinaba/c-structs-and-interface
В общем, структуры следует использовать для объектов, которые имеют семантику типа значения. Реализуя интерфейс в структуре, вы можете столкнуться с проблемами упаковки, поскольку структура передается туда и обратно между структурой и интерфейсом. В результате упаковки операции, которые изменяют внутреннее состояние структуры, могут работать некорректно.
IComparable<T>
иIEquatable<T>
. Сохранение структурыFoo
в переменной типаIComparable<Foo>
потребует упаковки, но если общий типT
ограниченIComparable<T>
одним, можно сравнить его с другим,T
не упаковывая ни один из них, и не зная ничего,T
кроме того, что он реализует ограничение. Такое выгодное поведение стало возможным только благодаря способности структур реализовывать интерфейсы. Как было сказано ...Поскольку этот ответ явно никто не дал, я добавлю следующее:
Реализация интерфейса в структуре не имеет никаких негативных последствий.
Любая переменная типа интерфейса, используемая для хранения структуры, приведет к использованию значения этой структуры в рамке. Если структура неизменяема (хорошо), то в худшем случае это проблема производительности, если только вы:
Оба варианта маловероятны, вместо этого вы, скорее всего, выполните одно из следующих действий:
Дженерики
Возможно, многие разумные причины для структур, реализующих интерфейсы, заключаются в том, что их можно использовать в общем контексте с ограничениями . При таком использовании переменная выглядит так:
class Foo<T> : IEquatable<Foo<T>> where T : IEquatable<T> { private readonly T a; public bool Equals(Foo<T> other) { return this.a.Equals(other.a); } }
new()
илиclass
.Тогда this.a НЕ является ссылкой на интерфейс, поэтому он не приводит к тому, что в него помещается ящик. Кроме того, когда компилятор C # компилирует универсальные классы и ему необходимо вставить вызовы методов экземпляра, определенных в экземплярах параметра Type T, он может использовать ограниченный код операции:
Это позволяет избежать упаковки, и поскольку тип значения реализует интерфейс, он должен реализовывать метод, поэтому упаковки не произойдет. В приведенном выше примере
Equals()
вызов выполняется без поля this . A 1 .API с низким коэффициентом трения
Большинство структур должны иметь примитивную семантику, в которой побитовые одинаковые значения считаются равными 2 . Среда выполнения предоставит такое поведение неявно,
Equals()
но это может быть медленным. Кроме того, это неявное равенство не отображается как реализацияIEquatable<T>
и, таким образом, предотвращает легкое использование структур в качестве ключей для словарей, если только они явно не реализуют его сами. Поэтому многие общедоступные типы структур обычно объявляют, что они реализуютIEquatable<T>
(гдеT
они сами), чтобы упростить и повысить производительность, а также согласовать с поведением многих существующих типов значений в CLR BCL.Все примитивы в BCL реализуют как минимум:
IComparable
IConvertible
IComparable<T>
IEquatable<T>
(И таким образомIEquatable
)Многие также реализуют
IFormattable
, кроме того, многие типы значений, определенные Системой, такие как DateTime, TimeSpan и Guid, также реализуют многие или все из них. Если вы реализуете аналогичный «широко полезный» тип, такой как структура со сложным числом или некоторые текстовые значения фиксированной ширины, то реализация многих из этих общих интерфейсов (правильная) сделает вашу структуру более полезной и удобной.Исключения
Очевидно, что если интерфейс явно подразумевает изменяемость (например,
ICollection
), то реализация этого - плохая идея, так как это будет означать, что вы либо сделали структуру изменяемой (что приводит к видам ошибок, описанным уже, когда изменения происходят в упакованном значении, а не в исходном ) или вы сбиваете с толку пользователей, игнорируя последствия таких методов, какAdd()
исключения или выдачу исключений.Многие интерфейсы НЕ предполагают изменчивости (например,
IFormattable
) и служат идиоматическим способом согласованного предоставления определенных функций. Часто пользователь структуры не заботится о накладных расходах для такого поведения.Резюме
Когда все сделано разумно, для неизменяемых типов значений хорошей идеей является реализация полезных интерфейсов.
Примечания:
1: Обратите внимание, что компилятор может использовать это при вызове виртуальных методов для переменных, которые, как известно, относятся к определенному типу структуры, но в которых требуется вызвать виртуальный метод. Например:
List<int> l = new List<int>(); foreach(var x in l) ;//no-op
Перечислитель, возвращаемый List, представляет собой структуру, оптимизацию, позволяющую избежать выделения при перечислении списка (с некоторыми интересными последствиями ). Однако семантика foreach указывает, что если перечислитель реализует,
IDisposable
тоDispose()
будет вызываться после завершения итерации. Очевидно, что если это произойдет через упакованный вызов, это исключит любую выгоду от того, что перечислитель является структурой (на самом деле это было бы хуже). Хуже того, если вызов dispose каким-либо образом изменяет состояние перечислителя, это может произойти с экземпляром в штучной упаковке, и в сложных случаях может появиться множество мелких ошибок. Следовательно, ИЛ, излучаемый в такой ситуации:Таким образом, реализация IDisposable не вызывает никаких проблем с производительностью, и (прискорбно) изменяемый аспект перечислителя сохраняется, если метод Dispose действительно что-то сделает!
2: double и float - исключения из этого правила, где значения NaN не считаются равными.
источник
struct
преобразовании вinterface
.В некоторых случаях для структуры может быть полезно реализовать интерфейс (если он никогда не был полезен, сомнительно, что создатели .net предусмотрели бы это). Если структура реализует интерфейс только для чтения, например
IEquatable<T>
, для хранения структуры в месте хранения (переменная, параметр, элемент массива и т. Д.) ТипаIEquatable<T>
потребуется, чтобы она была помещена в коробку (каждый тип структуры фактически определяет два типа вещей: хранилище тип местоположения, который ведет себя как тип значения и тип объекта кучи, который ведет себя как тип класса; первый неявно преобразуется во второй - "бокс" - а второй может быть преобразован в первый посредством явного приведения - «распаковка»). Однако можно использовать структурную реализацию интерфейса без упаковки, используя так называемые ограниченные обобщения.Например, если бы у кого-то был метод
CompareTwoThings<T>(T thing1, T thing2) where T:IComparable<T>
, такой метод можно было бы вызватьthing1.Compare(thing2)
без упаковкиthing1
илиthing2
. Еслиthing1
это, например, anInt32
, среда выполнения будет знать это, когда сгенерирует код дляCompareTwoThings<Int32>(Int32 thing1, Int32 thing2)
. Поскольку он будет знать точный тип как объекта, на котором размещен метод, так и объекта, который передается в качестве параметра, ему не придется упаковывать ни один из них.Самая большая проблема со структурами, реализующими интерфейсы, заключается в том, что структура, которая сохраняется в местоположении типа интерфейса
Object
, илиValueType
(в отличие от местоположения своего собственного типа), будет вести себя как объект класса. Для интерфейсов только для чтения это обычно не проблема, но для изменяющегося интерфейса, подобного которому,IEnumerator<T>
может возникнуть странная семантика.Рассмотрим, например, следующий код:
List<String> myList = [list containing a bunch of strings] var enumerator1 = myList.GetEnumerator(); // Struct of type List<String>.IEnumerator enumerator1.MoveNext(); // 1 var enumerator2 = enumerator1; enumerator2.MoveNext(); // 2 IEnumerator<string> enumerator3 = enumerator2; enumerator3.MoveNext(); // 3 IEnumerator<string> enumerator4 = enumerator3; enumerator4.MoveNext(); // 4
Отмеченный оператор №1 будет начинать
enumerator1
читать первый элемент. Состояние этого перечислителя будет скопировано вenumerator2
. Отмеченный оператор № 2 продвинет эту копию для чтения второго элемента, но не повлияетenumerator1
. Затем будет скопировано состояние этого второго перечислителяenumerator3
, которое будет продвинуто помеченным оператором # 3. Затем, посколькуenumerator3
иenumerator4
являются ссылочными типами, REFERENCE toenumerator3
будет скопирован вenumerator4
, поэтому отмеченный оператор будет эффективно продвигать обаenumerator3
иenumerator4
.Некоторые люди пытаются притвориться, что типы значений и ссылочные типы - это оба типа
Object
, но это не совсем так. Типы реальных значений могут быть преобразованы вObject
, но не являются его экземплярами. Экземпляр,List<String>.Enumerator
который хранится в местоположении этого типа, является типом значения и ведет себя как тип значения; копирование его в расположение типаIEnumerator<String>
преобразует его в ссылочный тип, и он будет вести себя как ссылочный тип . Последнее является своего родаObject
, а первое - нет.Кстати, еще пара примечаний: (1) В общем, изменяемые типы классов должны иметь свои
Equals
методы, проверяющие ссылочное равенство, но для упакованной структуры нет достойного способа сделать это; (2) несмотря на свое название,ValueType
это тип класса, а не тип значения; все типы, производные от,System.Enum
являются типами значений, как и все типы, производные от,ValueType
за исключениемSystem.Enum
, но обаValueType
иSystem.Enum
являются типами классов.источник
Структуры реализованы как типы значений, а классы - как ссылочные типы. Если у вас есть переменная типа Foo, и вы храните в ней экземпляр Fubar, она «упакует» ее в ссылочный тип, таким образом сводя на нет преимущество использования структуры в первую очередь.
Единственная причина, по которой я вижу использование структуры вместо класса, заключается в том, что это будет тип значения, а не ссылочный тип, но структура не может наследовать от класса. Если у вас есть структура, наследующая интерфейс, и вы передаете интерфейсы, вы теряете характер этого типа значения структуры. Можно просто сделать его классом, если вам нужны интерфейсы.
источник
(Ну, мне нечего добавить, но у меня еще нет навыков редактирования, так что поехали ..)
Совершенно безопасно. Нет ничего противозаконного в реализации интерфейсов в структурах. Однако вы должны спросить, зачем вам это нужно.
Однако получение ссылки на интерфейс для структуры поместит ее в коробку . Итак, штраф за производительность и так далее.
Единственный верный сценарий, о котором я могу думать прямо сейчас, проиллюстрирован в моем сообщении здесь . Если вы хотите изменить состояние структуры, хранящейся в коллекции, вам нужно будет сделать это через дополнительный интерфейс, представленный в структуре.
источник
Int32
методу, который принимает универсальный типT:IComparable<Int32>
(который может быть параметром универсального типа метода или классом метода), этот метод сможет использоватьCompare
метод для переданного объекта без его упаковки.Я думаю, проблема в том, что это вызывает упаковку, потому что структуры являются типами значений, поэтому есть небольшое снижение производительности.
Эта ссылка предполагает, что с ним могут быть другие проблемы ...
http://blogs.msdn.com/abhinaba/archive/2005/10/05/477238.aspx
источник
Нет никаких последствий для структуры, реализующей интерфейс. Например, встроенные системные структуры реализуют такие интерфейсы, как
IComparable
иIFormattable
.источник
У типа значения очень мало причин для реализации интерфейса. Поскольку вы не можете создать подкласс типа значения, вы всегда можете ссылаться на него как на его конкретный тип.
Если, конечно, у вас есть несколько структур, реализующих один и тот же интерфейс, тогда это может быть незначительно полезно, но на этом этапе я бы рекомендовал использовать класс и делать это правильно.
Конечно, реализуя интерфейс, вы упаковываете структуру, поэтому теперь она находится в куче, и вы больше не сможете передавать ее по значению ... Это действительно подтверждает мое мнение о том, что вам следует просто использовать класс в этой ситуации.
источник
IComparable
в бокс. Просто вызывая метод, который ожидаетIComparable
с типом значения, который его реализует, вы неявно помещаете тип значения в коробку.IComparable<T>
вызывать методы для структур типаT
без упаковки.Структуры похожи на классы, которые живут в стеке. Я не вижу причин, по которым они должны быть «небезопасными».
источник