Для структур действительно ли безопасно реализовать интерфейсы?

84
задан nawfal 13 January 2013 в 23:42
поделиться

8 ответов

Существует несколько вещей, продолжающихся в этом вопросе...

для структуры возможно реализовать интерфейс, но существуют проблемы, которые появляются с кастингом, переменчивостью и производительностью. Дополнительную информацию см. в этом сообщении: http://blogs.msdn.com/abhinaba/archive/2005/10/05/477238.aspx

В целом, структуры должны использоваться для объектов, которые имеют семантику типа значения. Путем реализации интерфейса на структуре можно столкнуться с упаковкой проблем, поскольку структура брошена и дальше между структурой и интерфейсом. В результате упаковки операции, которые изменяют внутреннее состояние структуры, не могут вести себя правильно.

45
ответ дан Scott Dorman 24 November 2019 в 08:28
поделиться

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

единственная причина я вижу для использования структуры вместо класса, то, потому что это будет тип значения и не ссылочный тип, но структура не может наследоваться классу. Если у Вас есть структура, наследовали интерфейс, и Вы раздаете интерфейсы, Вы теряете ту природу типа значения структуры. Мог бы также просто сделать его классом, если Вам нужны интерфейсы.

3
ответ дан dotnetengineer 24 November 2019 в 08:28
поделиться

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

Однако получение интерфейсной ссылки на структуру УПАКУЕТ это. Так потеря производительности и так далее.

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

3
ответ дан Community 24 November 2019 в 08:28
поделиться

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

Эта ссылка предполагает, что могли бы быть другие проблемы с нею...

http://blogs.msdn.com/abhinaba/archive/2005/10/05/477238.aspx

1
ответ дан Simon Keep 24 November 2019 в 08:28
поделиться

Нет никаких последствий для структуры, реализовывая интерфейс. Например, структуры встроенной системы реализуют интерфейсы как IComparable и IFormattable.

0
ответ дан Joseph Daigle 24 November 2019 в 08:28
поделиться

Существует очень мало причины типа значения для реализации интерфейса. Так как Вы не можете разделить тип значения на подклассы, можно всегда называть его его конкретным типом.

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

, Конечно, путем реализации интерфейса, Вы упаковываете структуру, таким образом, она теперь находится на "куче", и Вы не будете в состоянии передать ее значением больше... Это действительно укрепляет мое мнение, что необходимо просто использовать класс в этой ситуации.

0
ответ дан FlySwat 24 November 2019 в 08:28
поделиться

Структуры точно так же, как классы, которые живут в стеке. Я не вижу оснований, почему они должны быть "небезопасными".

-8
ответ дан Sklivvz 24 November 2019 в 08:28
поделиться

Поскольку никто другой явно не предоставил этот ответ, я добавлю следующее:

Реализация интерфейса в структуре не имеет никаких негативных последствий.

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

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

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

Generics

Возможно, многие разумные причины для структур, реализующих интерфейсы, заключаются в том, что их можно использовать в универсальном контексте с ограничениями . При таком использовании переменная выглядит так:

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);
    }
}
  1. Разрешить использование структуры в качестве параметра типа
    • до тех пор, пока не используется никакое другое ограничение, такое как new () или class .
  2. Разрешить избегать упаковки в структуры, используемые таким образом.

Тогда это .a НЕ является ссылкой на интерфейс, поэтому он не приводит к тому, что в него помещается блок. Кроме того, когда компилятор C # компилирует универсальные классы и ему необходимо вставить вызовы методов экземпляра, определенных в экземплярах параметра Type T, он может использовать код операции с ограничениями :

Если thisType является типом значения, а thisType реализует метод, тогда ptr передается без изменений как указатель this на инструкцию вызова метода для реализации метода с помощью thisType.

Это позволяет избежать упаковки, и поскольку тип значения реализует интерфейс, должен ] реализует метод, поэтому упаковки не произойдет. В приведенном выше примере вызов Equals () выполняется без рамки на this.a 1 .

API с низким коэффициентом трения

Большинство структур должны иметь семантику примитивного типа, где побитовые одинаковые значения считаются равными 2 . Среда выполнения предоставит такое поведение в неявном Equals () , но это может быть медленным. Также это неявное равенство не представлено как реализация IEquatable и, таким образом, предотвращает легкое использование структур в качестве ключей для словарей, если они явно не реализуют его сами. Поэтому многие общедоступные типы структур обычно заявляют, что они реализуют IEquatable (где T - это они сами), чтобы упростить и повысить производительность, а также согласовать с поведением многих существующих типов значений в CLR BCL.

Все примитивы в BCL реализуют как минимум:

  • IComparable
  • IConvertible
  • IComparable
  • IEquatable (И, таким образом, 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 каким-либо образом изменяет состояние перечислителя, это может произойти с экземпляром в штучной упаковке, и в сложных случаях может появиться множество тонких ошибок. Следовательно, IL, генерируемый в такой ситуации:

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 не считаются равными.

165
ответ дан 24 November 2019 в 08:28
поделиться
Другие вопросы по тегам:

Похожие вопросы: