Fluent interfaces, generics & type inference
От: Sinix  
Дата: 07.05.16 18:39
Оценка:
А вот кому загадку на поломать голову?
Сразу разрушу интригу: есть несколько решений, все неидеальные, но в принципе рабочие.
Т.е. вопрос не про "как решить мою проблему?", а про "как бы сделали вы?".

Итак, поехали.

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

Для того чтоб было понятно о чём речь — упрощённая (компилируется, но не работает) версия вот этого кода:
  cut
    struct RangeBoundaryFrom<T> // IEquatable, IComparable, операторы и проч. опущены для краткости.
    {
        public static bool operator >(RangeBoundaryFrom<T> a, RangeBoundaryFrom<T> b) => true;
        public static bool operator >=(RangeBoundaryFrom<T> a, RangeBoundaryFrom<T> b) => true;
        public static bool operator <(RangeBoundaryFrom<T> a, RangeBoundaryFrom<T> b) => true;
        public static bool operator <=(RangeBoundaryFrom<T> a, RangeBoundaryFrom<T> b) => true;

        public static bool operator >(RangeBoundaryFrom<T> a, RangeBoundaryTo<T> b) => true;
        public static bool operator >=(RangeBoundaryFrom<T> a, RangeBoundaryTo<T> b) => true;
        public static bool operator <(RangeBoundaryFrom<T> a, RangeBoundaryTo<T> b) => true;
        public static bool operator <=(RangeBoundaryFrom<T> a, RangeBoundaryTo<T> b) => true;

        public T Value { get; set; }
    }

    struct RangeBoundaryTo<T>   // то же самое.
    {
        public static bool operator >(RangeBoundaryTo<T> a, RangeBoundaryTo<T> b) => true;
        public static bool operator >=(RangeBoundaryTo<T> a, RangeBoundaryTo<T> b) => true;
        public static bool operator <(RangeBoundaryTo<T> a, RangeBoundaryTo<T> b) => true;
        public static bool operator <=(RangeBoundaryTo<T> a, RangeBoundaryTo<T> b) => true;

        public static bool operator >(RangeBoundaryTo<T> a, RangeBoundaryFrom<T> b) => true;
        public static bool operator >=(RangeBoundaryTo<T> a, RangeBoundaryFrom<T> b) => true;
        public static bool operator <(RangeBoundaryTo<T> a, RangeBoundaryFrom<T> b) => true;
        public static bool operator <=(RangeBoundaryTo<T> a, RangeBoundaryFrom<T> b) => true;

        public T Value { get; set; }
    }

    interface IRange<T>
    {
        RangeBoundaryFrom<T> From { get; }
        RangeBoundaryTo<T> To { get; }
    }
    interface IRangeFactory<T, TRange> : IRange<T> where TRange : IRange<T>
    {
        TRange Create(RangeBoundaryFrom<T> from, RangeBoundaryTo<T> to);
    }

    struct Range<T> : IRangeFactory<T, Range<T>>
    {
        public RangeBoundaryFrom<T> From { get; set; }
        public RangeBoundaryTo<T> To { get; set; }
        public Range<T> Create(RangeBoundaryFrom<T> from, RangeBoundaryTo<T> to) =>
            new Range<T>
            {
                From = from,
                To = to
            };
    }

    struct Range<T, TKey> : IRangeFactory<T, Range<T, TKey>>
    {
        public RangeBoundaryFrom<T> From { get; set; }
        public RangeBoundaryTo<T> To { get; set; }
        public TKey Key { get; set; }

        public Range<T, TKey> Create(RangeBoundaryFrom<T> from, RangeBoundaryTo<T> to) =>
            new Range<T, TKey>
            {
                From = from,
                To = to,
                Key = Key
            };
    }

и собственно проблема: нужно сделать extension-методы для этих структур. Что-то типа

    static class RangeExtension
    {
        public static Range<T> Union<T>(this Range<T> a, Range<T> b)=>
            a.Create(
                a.From <= b.From? a.From : b.From,
                a.To >= b.To ? a.To : b.To);

        public static Range<T> Intersect<T>(this Range<T> a, Range<T> b) =>
            a.Create(
                a.From >= b.From ? a.From : b.From,
                a.To <= b.To ? a.To : b.To);
    }

В чём подвох:

1. extension-методов будет что-то в районе двух десятков.

2. Они должны работать с любыми типами, реализующими IRangeFactory<T,TRange>. Как-то так:
            var a = new Range<int>();
            var b = new Range<int, string>();
            var c = new Range<int, double>();

            a = a.Union(b).Intersect(c);
Если генерить в лоб, то для каждого метода для пары типов Range<T>, Range<T, TKey> нужны будут 4 перегрузки. Добавится ещё один тип — понадобится уже 9 перегрузок и т. д.

3. Вызов методов не должен требовать указывать генерик-параметры, они должны выводиться из типов параметров.


Хорошие новости:
* Считаем, что у нас есть кодогенерация и количество типов известно при компиляции. Т.е. даже самый тупой вариант в лоб (N^2 перегрузок) сойдёт за рабочий, хоть и некрасивый

Какие будут идеи?

P.S. Бонус-левел: метод .Create() из IRangeFactory желательно спрятать из public API, например, оформив как explicit interface implementation.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.