А вот кому загадку на поломать голову?
Сразу разрушу интригу: есть несколько решений, все неидеальные, но в принципе рабочие.
Т.е. вопрос не про "как решить мою проблему?", а про "как бы сделали вы?".
Итак, поехали.
Дано: есть несколько структур, они реализуют общий генерик-интерфейс.
Требуется поверх этого интерфейса нагородить стопку 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.