Объявление структуры, возвращаемой из метода интерфейса - PullRequest
6 голосов
/ 07 января 2010

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

class MyWidget
{
    struct LittleThing
    {
        int foo;
        DateTime bar;
    }

    public IList<LittleThing> LookupThings()
    {
        // etc.
    }
}

Однако я должен объявить этот метод в интерфейсе. Звонящий не видит MyWidget, только интерфейс IWidget. Вышеуказанная настройка не работает в этой ситуации, потому что C # не позволяет определять типы внутри интерфейса. Как правильно или лучше всего сделать такое заявление?

Простая вещь, о которой я подумал, - это просто объявить LittleThing вне интерфейса. Это не кажется хорошим, по нескольким причинам. Первый: он используется только этим единственным методом в этом единственном классе, поэтому не кажется, что LittleThing должен быть независимым типом, просто плавающим сам по себе. Второе: если аналогичные методы будут написаны для других классов, они будут возвращать разные типы данных (по уважительным причинам), и я не хочу загромождать пространство имен тонной структур с одинаковыми именами, которые отличаются незначительно друг от друга.

Если бы мы могли обновить нашу версию .Net, я бы просто вернул Tuple<>, но это еще не будет вариант в течение некоторого времени.

[Отредактировано, чтобы добавить: маленький объект должен содержать более двух полей, поэтому KeyValuePair<K,V> не совсем обрежет его.]

[Отредактировано, чтобы добавить дальше: IWidget реализован только одним классом, Widget. Я думаю, что странно иметь интерфейс только для одного класса, но это было сделано, чтобы удовлетворить старую политику кодирования, которая требовала, чтобы контракт всегда был в отдельной сборке от реализации. Указанная политика исчезла, но у нас нет ресурсов, чтобы провести рефакторинг всего приложения и удалить все ненужные интерфейсы.]

Какая лучшая практика?

Ответы [ 7 ]

6 голосов
/ 07 января 2010

Если «LittleThing» имеет только два значения, вы можете вернуть KeyValuePair<TKey,TValue>.

Если их больше двух, вы всегда можете создать свой собственный класс Tuple и заменить его на .NET 4, когда наконец перейдете на .NET 4.

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

2 голосов
/ 07 января 2010
  1. Зачем использовать структуры? Почему бы не использовать классы вместо этого?
  2. Объявите класс отдельно, как открытый класс.
1 голос
/ 07 января 2010

Если бы мы могли обновить нашу версию .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 выведем их

1 голос
/ 07 января 2010

Вы можете объявить struct вне interface, но внутри вложенного namespace.

0 голосов
/ 08 января 2010

Итак, прочитав все, что вы прокомментировали здесь, я думаю:

  1. Если все типы, реализующие IWidget, возвращают LittleThing:

    Тогда я считаю, что наилучшая практика для LittleThing - это тот же уровень пространства имен, что и для IWidget, предпочтительно в пространстве имен Widgets. Однако вы действительно должны подумать о том, чтобы сделать LittleThing классом. По вашему описанию проблемы кажется, что она может быть не такой маленькой, так как каждый раз, когда класс, реализующий IWidget, неправильно использует одни поля, но не другие.

  2. Если все типы, реализующие IWidget, должны возвращать немного разные значения, но с похожим поведением:

    Подумайте о создании интерфейса IThing. Тогда каждая реализация IThing будет полностью зависеть от класса, который его возвращает, поэтому вы можете объявить структуру или класс, реализующий IThing внутри класса, реализующего IWidget, следующим образом:

    interface IWidget
    {
        IList<IThing> LookupThings()
        {
            …
        }
    }
    
    interface IThing
    {
        …
    }
    
    class MyWidget : IWidget
    {
        IList<IThing> IWidget.LookupThings()
        {
             …
        }
    
        private class MyWidgetThings : IThing
        {
             …
        }
    }
    
0 голосов
/ 08 января 2010

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

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

public interface IWidget
{
    IList<Widget.LittleThing> LookupThings();
}

// For definitions used by IWidget
public class Widget
{
    public struct LittleThing
    {
        int foo;
        DateTime bar;
    }
}

Примеры этого шаблона можно найти в BCL, в частности, с обобщениями и методами расширения, а также со значениями по умолчанию (например, EqualityComparer<T>.Default) и даже реализациями по умолчанию (например, IList<T> и List<T>). Выше приведен еще один случай.

0 голосов
/ 07 января 2010

Игнорируя значение имени структуры, вы можете использовать общую версию KeyValuePair .

...