Я, кажется, помню, что читал что-то о том, как плохо для структур реализовывать интерфейсы в CLR через C#, но я не могу найти ничего об этом. Это плохо? Есть ли у этого непредвиденные последствия?
public interface Foo { Bar GetBar(); }
public struct Fubar : Foo { public Bar GetBar() { return new Bar(); } }





Структуры похожи на классы, которые живут в стеке. Я не вижу причин, по которым они должны быть «небезопасными».
Я не могу согласиться с каждой частью этого ответа; они не обязательно живут в стеке, а семантика копирования - Очень разные для классов.
Они неизменны, чрезмерное использование struct сделает вашу память грустной :(
@Teomanshipahi Чрезмерное использование экземпляров классов сведет ваш сборщик мусора с ума.
@ IllidanS4 использует IDisposable и оператор using, это сделает ваш сборщик мусора счастливым.
Для тех, у кого более 20к репутации, такой ответ просто неприемлем.
Нет никаких последствий для структуры, реализующей интерфейс. Например, встроенные системные структуры реализуют такие интерфейсы, как IComparable и IFormattable.
У типа значения очень мало причин для реализации интерфейса. Поскольку вы не можете создать подкласс типа значения, вы всегда можете ссылаться на него как на его конкретный тип.
Если, конечно, у вас есть несколько структур, реализующих один и тот же интерфейс, тогда это может быть незначительно полезно, но на этом этапе я бы рекомендовал использовать класс и делать это правильно.
Конечно, реализуя интерфейс, вы упаковываете структуру, поэтому теперь она находится в куче, и вы больше не сможете передавать ее по значению ... Это действительно укрепляет мое мнение, что вам следует просто использовать класс в этой ситуации.
Как часто вы передаете IComparable вместо конкретной реализации?
Вам не нужно передавать IComparable, чтобы упаковать значение. Просто вызвав метод, который ожидает IComparable с типом значения, который его реализует, вы неявно поместите тип значения в коробку.
@AndrewHare: дженерики с ограничениями позволяют вызывать методы на IComparable<T> в структурах типа T без упаковки.
Я думаю, проблема в том, что это вызывает упаковку, потому что структуры являются типами значений, поэтому есть небольшое снижение производительности.
Эта ссылка предполагает, что с ним могут быть другие проблемы ...
http://blogs.msdn.com/abhinaba/archive/2005/10/05/477238.aspx
Структуры реализованы как типы значений, а классы - как ссылочные типы. Если у вас есть переменная типа Foo, и вы храните в ней экземпляр Fubar, она «упакует» ее в ссылочный тип, таким образом сводя на нет преимущество использования структуры в первую очередь.
Единственная причина, по которой я вижу использование структуры вместо класса, заключается в том, что это будет тип значения, а не ссылочный тип, но структура не может наследовать от класса. Если у вас есть структура, наследующая интерфейс, и вы передаете интерфейсы, вы теряете характер этого типа значения структуры. Можно просто сделать его классом, если вам нужны интерфейсы.
Это работает и для примитивов, реализующих интерфейсы?
В этом вопросе происходит несколько вещей ...
Структура может реализовать интерфейс, но есть проблемы, связанные с приведением типов, изменчивостью и производительностью. См. Этот пост для более подробной информации: https://docs.microsoft.com/en-us/archive/blogs/abhinaba/c-structs-and-interface
Как правило, структуры следует использовать для объектов, имеющих семантику типа значения. Реализуя интерфейс в структуре, вы можете столкнуться с проблемами бокса, поскольку структура передается назад и вперед между структурой и интерфейсом. В результате упаковки операции, которые изменяют внутреннее состояние структуры, могут работать некорректно.
«В результате упаковки операции, которые изменяют внутреннее состояние структуры, могут работать некорректно». Приведите пример и получите ответ.
@Will: Не уверен, о чем вы говорите в своем комментарии. В сообщении в блоге, на которое я ссылался, есть пример, который показывает, где вызов метода интерфейса в структуре на самом деле не изменяет внутреннее значение.
@Will: CLR Via C# подробно рассматривает эту проблему в главе "Типы ссылок и значений" (3?) ... Я бы напечатал ее, но я ухожу на работу, поэтому, если у вас есть эта книга, взгляните .
@ScottDorman: В некоторых случаях наличие структур, реализующих интерфейсы, может помочь боксу избегать. Яркие примеры - IComparable<T> и IEquatable<T>. Сохранение структуры Foo в переменной типа IComparable<Foo> потребует упаковки, но если общий тип T ограничен IComparable<T>, можно сравнить его с другим T, не упаковывая ни один из них, и не имея необходимости знать что-либо о T, кроме того, что он реализует ограничение. Такое выгодное поведение стало возможным только благодаря способности структур реализовывать интерфейсы. Это было сказано ...
... было бы неплохо, если бы существовали средства объявления того, что конкретный интерфейс следует считать применимым только к неупакованным структурам, поскольку есть некоторые контексты, в которых объект класса или упакованная структура не могут иметь желаемое поведение.
«структуры должны использоваться для объектов, которые имеют семантику типа значения. ... операции, которые изменяют внутреннее состояние структуры, могут не вести себя должным образом». Разве настоящая проблема не в том, что семантика типа значения и изменчивость плохо сочетаются?
Древняя ссылка мертва.
(Ну, мне нечего добавить, но у меня еще нет навыков редактирования, так что вперед ..)
Совершенно безопасно. Нет ничего противозаконного в реализации интерфейсов в структурах. Однако вы должны спросить, зачем вам это нужно.
Однако получение ссылки на интерфейс для структуры будет BOX это. Так что штраф за производительность и так далее.
Единственный допустимый сценарий, о котором я могу думать прямо сейчас, - это проиллюстрировано в моем сообщении здесь. Если вы хотите изменить состояние структуры, хранящейся в коллекции, вам придется сделать это через дополнительный интерфейс, представленный в структуре.
Если передать Int32 методу, который принимает общий тип T:IComparable<Int32> (который может быть параметром универсального типа для метода или классом метода), этот метод сможет использовать метод Compare для переданного объекта без бокс это.
Поскольку никто другой явно не предоставил этот ответ, я добавлю следующее:
Реализация интерфейс в структуре не имеет никаких негативных последствий.
Любой Переменная типа интерфейса, используемого для хранения структуры, приведет к использованию упакованного значения этой структуры. Если структура неизменяема (хорошо), то в худшем случае это проблема производительности, если только вы:
Оба варианта маловероятны, вместо этого вы, скорее всего, выполните одно из следующих действий:
Возможно, многие разумные причины для структур, реализующих интерфейсы, заключаются в том, что их можно использовать в контексте общий с ограничения. При таком использовании переменная выглядит так:
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, он может использовать код операции сдержанный:
If thisType is a value type and thisType implements method then ptr is passed unmodified as the 'this' pointer to a call method instruction, for the implementation of method by thisType.
Это позволяет избежать упаковки, и, поскольку тип значения реализует интерфейс, должен реализует метод, поэтому упаковки не произойдет. В приведенном выше примере вызов Equals() выполняется без поля this.a1.
Большинство структур должны иметь примитивную семантику, в которой побитовые одинаковые значения считаются равными 2. Среда выполнения предоставит такое поведение в неявном Equals(), но это может быть медленным. Также это неявное равенство нет представлено как реализация IEquatable<T> и, таким образом, предотвращает легкое использование структур в качестве ключей для словарей, если они сами явно не реализуют его. Поэтому многие общедоступные типы структур обычно объявляют, что они реализуют IEquatable<T> (где T - это они сами), чтобы упростить и повысить производительность, а также согласовать с поведением многих существующих типов значений в CLR BCL.
Все примитивы в BCL реализуют как минимум:
IComparableIConvertibleIComparable<T>IEquatable<T> (и, следовательно, IEquatable)Многие также реализуют IFormattable, кроме того, многие типы значений, определенные Системой, такие как DateTime, TimeSpan и Guid, также реализуют многие или все из них. Если вы реализуете аналогичный «широко полезный» тип, такой как структура со сложным числом или некоторые текстовые значения фиксированной ширины, то реализация многих из этих общих интерфейсов (правильно) сделает вашу структуру более полезной и удобной.
Очевидно, что если интерфейс строго подразумевает изменчивость (например, ICollection), тогда реализация этого будет плохой идеей, так как это будет означать, что вы либо сделали структуру изменяемой (что приводит к видам ошибок, описанным уже, когда изменения происходят в упакованном значении, а не в original), или вы сбиваете с толку пользователей, игнорируя последствия таких методов, как Add(), или вызывая исключения.
Многие интерфейсы НЕ предполагают изменчивости (например, IFormattable) и служат идиоматическим способом согласованного предоставления определенных функций. Часто пользователь структуры не заботится о накладных расходах на бокс для такого поведения.
Если все сделано разумно, для неизменяемых типов значений хорошей идеей является реализация полезных интерфейсов.
1: Обратите внимание, что компилятор может использовать это при вызове виртуальных методов для переменных, которые известен относятся к определенному типу структуры, но в которых требуется вызывать виртуальный метод. Например:
List<int> l = new List<int>();
foreach(var x in l)
;//no-op
Перечислитель, возвращаемый List, представляет собой структуру, оптимизацию, позволяющую избежать выделения при перечислении списка (с некоторым интересным последствия). Однако семантика foreach указывает, что если перечислитель реализует IDisposable, то Dispose() будет вызываться после завершения итерации. Очевидно, что если это произойдет через упакованный вызов, это исключит любую выгоду от того, что перечислитель является структурой (на самом деле, это было бы хуже). Хуже того, если вызов dispose каким-либо образом изменяет состояние перечислителя, это может произойти с экземпляром в штучной упаковке, и в сложных случаях может появиться множество мелких ошибок. Следовательно, ИЛ, излучаемый в такой ситуации:
IL_0001: newobj System.Collections.Generic.List..ctor IL_0006: stloc.0 IL_0007: nop IL_0008: ldloc.0 IL_0009: callvirt System.Collections.Generic.List.GetEnumerator IL_000E: stloc.2 IL_000F: br.s IL_0019 IL_0011: ldloca.s 02 IL_0013: call System.Collections.Generic.List.get_Current IL_0018: stloc.1 IL_0019: ldloca.s 02 IL_001B: call System.Collections.Generic.List.MoveNext IL_0020: stloc.3 IL_0021: ldloc.3 IL_0022: brtrue.s IL_0011 IL_0024: leave.s IL_0035 IL_0026: ldloca.s 02 IL_0028: constrained. System.Collections.Generic.List.Enumerator IL_002E: callvirt System.IDisposable.Dispose IL_0033: nop IL_0034: endfinally
Таким образом, реализация IDisposable не вызывает никаких проблем с производительностью, и (прискорбно) изменяемый аспект перечислителя сохраняется, если метод Dispose действительно что-то сделает!
2: double и float - исключения из этого правила, где значения NaN не считаются равными.
Сайт egheadcafe.com был перемещен, но его содержимое не сохранилось. Я пробовал, но не могу найти исходный документ eggheadcafe.com/software/aspnet/31702392/…, не зная OP. (PS +1 за отличное резюме).
Это отличный ответ, но я думаю, что вы можете улучшить его, переместив «Сводку» наверх как «TL; DR». Предоставление заключения сначала помогает читателю понять, к чему вы идете.
При преобразовании struct в interface должно появиться предупреждение компилятора.
В некоторых случаях для структуры может быть полезно реализовать интерфейс (если он никогда не был полезен, сомнительно, что создатели .net предусмотрели бы это). Если структура реализует интерфейс только для чтения, такой как IEquatable<T>, для сохранения структуры в месте хранения (переменная, параметр, элемент массива и т. д.) Типа IEquatable<T> потребуется упаковка (каждый тип структуры фактически определяет два типа вещей: тип места хранения, который ведет себя как тип значения, и тип объекта кучи, который ведет себя как тип класса; первый неявно преобразуется во второй - "бокс" - а второй может быть преобразован в первый посредством явного приведения - «распаковка»). Однако можно использовать структурную реализацию интерфейса без упаковки, используя так называемые ограниченные обобщения.
Например, если у кого-то был метод CompareTwoThings<T>(T thing1, T thing2) where T:IComparable<T>, такой метод мог бы вызывать thing1.Compare(thing2) без необходимости вставлять thing1 или thing2 в коробку. Если thing1 окажется, например, Int32, среда выполнения будет знать об этом, когда сгенерирует код для 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 являются ссылочными типами, ССЫЛКА в enumerator3 будет скопирован в enumerator4, поэтому отмеченный оператор будет эффективно продвигать обеenumerator3 и enumerator4.
Некоторые люди пытаются представить, что типы значений и ссылочные типы - это оба типа Object, но это не совсем так. Типы реальных значений могут быть преобразованы в Object, но не являются его экземплярами. Экземпляр List<String>.Enumerator, который хранится в местоположении этого типа, является типом значения и ведет себя как тип значения; копирование в расположение типа IEnumerator<String> преобразует его в ссылочный тип, и он будет вести себя как ссылочный тип. Последний является разновидностью Object, а первый - нет.
Кстати, еще пара примечаний: (1) В общем, изменяемые типы классов должны иметь их методы Equals, проверяющие эталонное равенство, но для упакованной структуры нет достойного способа сделать это; (2) несмотря на свое название, ValueType является типом класса, а не типом значения; все типы, производные от System.Enum, являются типами значений, как и все типы, производные от ValueType, за исключением System.Enum, но как ValueType, так и System.Enum являются типами классов.
За исключением того, что они лишены наследственности.