Я пишу метод в C# (2.0), который должен возвратить набор простых объектов. Обычно я сделал бы что-то вроде этого:
class MyWidget
{
struct LittleThing
{
int foo;
DateTime bar;
}
public IList<LittleThing> LookupThings()
{
// etc.
}
}
Однако я должен объявить этот метод в интерфейсе. Вызывающая сторона не добирается для наблюдения MyWidget
, только IWidget
интерфейс. Вышеупомянутая установка не работает в той ситуации, потому что C# не позволяет определять типы в интерфейсе. Что надлежащий или лучший способ состоит в том, чтобы сделать такое объявление?
Простая вещь, о которой я думал, состоит в том, чтобы просто объявить LittleThing
за пределами интерфейса. Это не кажется большим по нескольким причинам. Один: это только когда-либо используется тем отдельным методом в том едином классе, таким образом, это не кажется этим LittleThing
должен быть независимый тип, просто плавающий вокруг отдельно. Два: если похожие методы завершат то, чтобы быть записанным для других классов, то они будут возвращать различные виды данных (по хорошим причинам дизайна), и я не хочу создавать помехи namepace тонной подобно названных структур, которые отличаются незначительно друг от друга.
Если бы мы могли бы обновить нашу версию .NET, я просто возвратил бы a Tuple<>
, но это в течение некоторого времени еще не будет опцией.
[Отредактированный для добавления: маленький объект действительно должен содержать больше чем два поля, таким образом, KeyValuePair<K,V>
не вполне сократит его.]
[Отредактированный для добавления далее: IWidget
реализован только одним классом, Widget
. Я думаю, что это странный имеет интерфейс только для одного класса, но это было сделано для удовлетворения старой политики кодирования, которая потребовала, чтобы контракт всегда был в отдельном блоке от реализации. Упомянутая политика теперь ушла, но у нас нет ресурсов, чтобы осуществить рефакторинг целое приложение и удалить все ненужные интерфейсы.]
Какова лучшая практика?
Если бы мы могли обновить нашу версию .Net, я бы просто вернул Tuple<>, но это еще не будет вариантом в течение некоторого времени.
Зачем ждать? Не похоже, что кортеж - это сложная вещь. Вот код для 3-ступенчатого кортежа.
public struct Tuple<TItem1, TItem2, TItem3>
{
public Tuple(TItem1 item1, TItem2 item2, TItem3 item3)
{
this = new Tuple<TItem1, TItem2, TItem3>();
Item1 = item1;
Item2 = item2;
Item3 = item3;
}
public static bool operator !=(Tuple<TItem1, TItem2, TItem3> left, Tuple<TItem1, TItem2, TItem3> right)
{ return left.Equals(right); }
public static bool operator ==(Tuple<TItem1, TItem2, TItem3> left, Tuple<TItem1, TItem2, TItem3> right)
{ return !left.Equals(right); }
public TItem1 Item1 { get; private set; }
public TItem2 Item2 { get; private set; }
public TItem3 Item3 { get; private set; }
public override bool Equals(object obj)
{
if (obj is Tuple<TItem1, TItem2, TItem3>)
{
var other = (Tuple<TItem1, TItem2, TItem3>)obj;
return Object.Equals(Item1, other.Item1)
&& Object.Equals(Item2, other.Item2)
&& Object.Equals(Item3, other.Item3);
}
return false;
}
public override int GetHashCode()
{
return ((this.Item1 != null) ? this.Item1.GetHashCode() : 0)
^ ((this.Item2 != null) ? this.Item2.GetHashCode() : 0)
^ ((this.Item3 != null) ? this.Item3.GetHashCode() : 0);
}
}
Как видите, ничего страшного. Что я сделал в своем текущем проекте, так это реализовал 2, 3 и 4-ступени, а также статический Tuple
класс с Create
методами, которые в точности повторяют типы кортежей .NET 4. Если вы действительно параноик, вы можете использовать рефлектор, чтобы посмотреть на разобранный исходный код кортежа .NET 4, и скопировать его дословно
Когда мы в конце концов обновим до .NET 4, мы просто удалим классы, или #ifdef
их из
Игнорируя здесь значение названия структуры, можно использовать общую версию KeyValuePair.
.Интерфейсы определяют только то, что может быть реализовано на другом классе, поэтому внутри них ничего нельзя дать определения. Включая классы и структуры.
Тем не менее, одним из паттернов, часто используемых для обхода этого ограничения, является определение класса с тем же именем (исключая префикс 'I') для предоставления любых связанных определений, таких как:
public interface IWidget
{
IList<Widget.LittleThing> LookupThings();
}
// For definitions used by IWidget
public class Widget
{
public struct LittleThing
{
int foo;
DateTime bar;
}
}
Примеры этого паттерна можно найти в BCL, в частности, в генериках и методах-расширениях, но также и со значениями по умолчанию (e. например, EqualityComparer
) и даже реализации по умолчанию (например, IList
и List
). Выше приведен лишь еще один случай.
Если "LittleThing" имеет только два значения, вы можете вернуть KeyValuePair
.
Если их больше двух, вы всегда можете сделать свой собственный класс Tuple, и заменить его на .NET 4, когда вы, наконец, перейдете на .NET 4.
Иначе я бы просто определил структуру с интерфейсом, и включил бы ее как часть вашего API. Пространства имен заботятся об именовании...
Вы можете объявить структуру
вне интерфейса
, но внутри вложенного пространства имен
.
Итак, прочитав все, что вы прокомментировали, я вот что думаю:
Если все типы, реализующие IWidget, возвращают LittleThing:
Тогда я считаю, что лучшая практика для LittleThing должна быть на том же уровне пространства имен чем IWidget, предпочтительно в пространстве имен Widgets. Однако вам действительно стоит подумать о том, чтобы сделать LittleThing классом. Судя по вашему описанию проблемы, это может быть не так уж и мало, поскольку каждый раз класс, реализующий IWidget, неправильно использует одни поля, но не другие.
Если все типы, реализующие IWidget, должны возвращать немного разные значения, но с аналогичным поведением:
Рассмотрите возможность создания интерфейса IThing. Тогда каждая реализация IThing будет полностью зависеть от класса, который ее возвращает, поэтому вы можете объявить структуру или класс, реализующий IThing внутри класса, реализующего IWidget, следующим образом:
interface IWidget
{
IList LookupThings ()
{
…
}
}
интерфейс IThing
{
…
}
класс MyWidget: IWidget
{
IList IWidget.LookupThings ()
{
…
}
частный класс MyWidgetThings: IThing
{
…
}
}