Тип вывода для свободного API

У меня есть следующие методы расширения:

public static IFoo Foo(this IFluentApi api, Action action);

public static IFoo<TResult> Foo<TResult>(
    this IFluentApi api, Func<TResult> func);

public static IBar Bar(this IFoo foo);

public static void FooBar(this IBar bar, Action action);

public static void FooBar<TResult>( // <- this one cannot work as desired 
    this IBar bar, Action<TResult> action);

Общие интерфейсы всегда выводятся из их соответствующего не общего интерфейса.

К сожалению, для выполнения этой работы:

api.Foo(x => ReturnLong())
   .Bar()
   .FooBar(x => ...); // x should be of type long

Мне также необходимо реализовать следующий метод расширения:

public static IBar<TResult> Bar<TResult> (this IFoo<TResult> foo);

и измените последний из указанных выше методов расширения на:

public static void FooBar<TResult>(
    this IBar<TResult> bar, Action<TResult> action);

Поскольку у меня на самом деле не только Bar() между Foo() и FooBar(), но и ОЧЕНЬ длинная цепочка методов, у меня были бы огромные дополнительные затраты на внедрение.

Есть ли способ избежать этой проблемы и "волшебным образом" передать общий параметр TResult?

Изменить:

Без потери вывода типа!

Ответ 1

Предполагая, что вы можете перейти от IFoo<TResult> к IFoo, а ваша цепочка методов не заботится о TResult, вы можете сохранить часть реализации, изменив использование на что-то вроде:

api.Foo(x => ReturnLong())
   .Bars(foo=>foo.Bar1() //where foo is an IFoo
                 .Bar2()
                 .Bar3()
                 ...
    )
   .FooBar(x => ...);

Ответ 2

Избавьтесь от интерфейса IFoo, у которого нет параметра типа, и добавьте параметр типа IBar для запоминания типа из IFoo. Без этого неправильные программы будут проверять тип.

public interface IFluentApi {}

public interface IFoo<T> {}

public interface IBar<T> {}

public struct Unit {}

public static class Extenders
{
    public static IFoo<Unit> Foo(this IFluentApi api, Action action) {return null;}

    public static IFoo<T> Foo<T>(this IFluentApi api, Func<T> func) {return null;}

    public static IBar<T> Bar<T>(this IFoo<T> foo) {return null;}

    public static void FooBar<T>(this IBar<T> bar, Action action) {}

    public static void FooBar<T>(this IBar<T> bar, Action<T> action) {}

    public static void CheckType<T>(this T value) {}
}

public class Examples
{
    public void Example()
    {

        IFluentApi api = null;

        api.Foo(() => "Value")
           .Bar()
           .FooBar(x => x.CheckType<string>()); // x is a string


        api.Foo(() => {})
           .Bar()
           .FooBar(x => x.CheckType<Unit>() ); // x is a Unit

        // The following (correctly) fails to type check
        Action<string> stringAction = Console.WriteLine;
        api.Foo(() => (long) 7)
           .Bar()
           .FooBar(stringAction); // x should be of type long
    }
}

Ответ 3

Свободный интерфейс внутри С# зависит от типов (явно или неявно), проходящих через каждый .. Как вы описали, если вы потеряете информацию о типе, вы не сможете ее вернуть.

Ваши единственные варианты - иметь ветки в ваших выражениях, как описано в ответе Шона, или вы должны иметь только IBar<TResult> Bar<TResult> (this IFoo<TResult> foo), так что информация о типе, которая требуется, всегда передается.

  • Конечно, если некоторые из ваших .Bar на самом деле похожи на .First или .SingleOrDefault, они не должны следовать .FooBar в любом случае (по крайней мере, не напрямую).

Ответ 4

Обратите внимание, что общий тип должен быть известен во время компиляции. Вы можете сохранить экземпляр Type Class во время выполнения, но вы не сможете использовать его вместо общего параметра.

Вы создали два типа: IFoo и IFoo<T> : IFoo. Однако класс IBar создается IFoo, который не имеет информации о его типе, поскольку он не имеет хоста. Таким образом, информация о типе теряется. Мы можем рассматривать только решения, способные вывести тип во время компиляции.


Вам известно первое решение - создание универсальных версий всех типов, которые вы используете в цепочке вызовов. Это требует больших усилий.


Если вы можете предположить, что тип не изменится во время выполнения, то вы можете обернуть этот тип и явно использовать свои методы:

api.Foo(() => default(long))
   .Bar()
   .FooBar<long>(x => { });

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


Третий и очень гибкий подход - избавиться от дженериков в пользу простых объектов:

void FooBar(this IBar bar, Action<object> action) { /* ... */ }

.
.
.

api.Foo(() => default(long))
   .Bar()
   .FooBar(x => { }); // <-- That will compile, but x is an object

Обратите внимание, что FooBar отвечает за отправку аргумента для действия. Поэтому вы можете просто проверить во время выполнения тип объекта, с которым имеете дело:

.
.
.FooBar(x => { if (x is MyType) { /* ... */ } });

Посредством отражения вы можете получить всю необходимую информацию об x.