Использование ограниченных обобщений вместо интерфейсов - недостатки? - PullRequest
8 голосов
/ 04 декабря 2011

Допустим, у меня есть

interface IMatrix {
    double this[int r, int c] { get; }
}

struct Matrix2x2 : IMatrix  {
    double a1, a2, b1, b2;
    double this[int r, int c] { get { ... } }
}

struct Matrix3x3 : IMatrix {
    double a1, a2, a3, b1, b2, b3, c1, c2, c3;
    double this[int r, int c] { get { ... } }
}

class Matrix : IMatrix {    // Any size
    double[,] cells;
    double this[int r, int c] { get { ... } }
}

Иногда вместо того, чтобы просто сказать

static class Matrices {
    static IMatrix Multiply(IMatrix a, IMatrix b) { ... }
}

Я заканчиваю тем, что делаю

static class Matrices {
    static IMatrix Multiply<T1, T2>(T1 a, T2 b)
        where T1 : IMatrix
        where T2 : IMatrix { ... }
}

или, может быть, даже

static class Matrices {
    static IMatrix Multiply<T1, T2>([In] ref T1 a, [In] ref T2 b)
        where T1 : IMatrix
        where T2 : IMatrix { ... }
}

, чтобы избежать упаковки или копирования struct s.

Работает нормально и все такое, но есть ли минусы, о которых я не знаю (кроме незначительного увеличения использования памяти)? Является ли это принятой практикой или не рекомендуется по какой-либо причине, о которой я мог не знать?

Ответы [ 2 ]

4 голосов
/ 04 декабря 2011

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

1 голос
/ 07 декабря 2011

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

В качестве другого примера различия между эталоном и значением предположим, что метод имеет матрицу, и он хочет иметь матрицу, аналогичную старой, за исключением элементов (0,0) и (1,1) обнуляется, и ей больше не понадобится исходная матрица. Если матрица является изменяемым типом значения с настраиваемым индексированным свойством, метод может просто записывать в элементы (0,0) и (1,1) без нежелательных побочных эффектов; если это изменяемый ссылочный тип, то, скорее всего, рутине сначала нужно сделать защитный клон (ick); если это неизменяемый тип, может потребоваться создать новый экземпляр для каждой модификации (ick). Использование типа значения может предложить намного более чистую семантику, но может вызвать неожиданные побочные эффекты, если система выполняет преобразования кода, такие как боксирование, которые изменяют семантику типа значения в ссылочную семантику или поврежденную семантику.

(*) Структура, которая просто предоставляет изменяемые поля, демонстрирует очень чистую изменяемую семантику типа значения. Учитывая декларации:

public struct valueStruct {public int value; ... }
public interface IManipulateValueStruct {public void manipulate(ref valueStruct it);}
public void someMethod(IManipulateValueStruct manipulator1, manipulator2)
{
  valueStruct x,y;
  ...
  manipulator1.Manipulate(ref x);
  manipulator2.Manipulate(ref y);  // What does this method do with x and y?
  ...
}

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

Хотя структуры с открытыми полями реализуют изменяемую семантику значений, структуры могут реализовывать изменяемую ссылочную семантику, если они содержат неизменяемое поле типа класса, и если на них действуют только те мутаторы, которые они предоставляют. Иногда это может быть полезным шаблоном, хотя и несколько ограниченным, потому что компиляторы C # и vb запрещают операции, которые выглядят так, как будто они могут пытаться изменить структуру (но на самом деле будут изменять объект класса, на который он ссылается). Обратите внимание, что неизменяемые структуры с изменяемой ссылочной семантикой должны быть инициализированы конструктором не по умолчанию, чтобы быть полезным. Такие структуры, которые создаются с помощью инициализации по умолчанию, как правило, будут сломаны и бесполезны; в лучшем случае они будут неотличимы друг от друга.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...