用于流畅API的类型推断

时间:2013-07-24 18:55:36

标签: c# generics fluent fluent-interface

我有以下扩展方法:

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通用参数?

修改:

不丢失类型推断!

4 个答案:

答案 0 :(得分:4)

假设您能够从IFoo<TResult>转到IFoo并且您的方法链不关心TResult,您可以通过更改来保存部分实施方案用法如下:

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

答案 1 :(得分: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
    }
}

答案 2 :(得分:1)

C#中的流畅界面依赖于通过每个 . 传递的类型(显式或隐式)。正如您所描述的,如果您丢失了类型信息,则无法将其恢复。

您唯一的选择是在表达式中使用分支,如Shawn的答案中所述,或者您必须只有IBar<TResult> Bar<TResult> (this IFoo<TResult> foo),以便始终传递所需的类型信息。

  • 当然,如果您的某些.Bar实际上与.First.SingleOrDefault相同,则无论如何都不应该.FooBar(至少不是直接)。

答案 3 :(得分:0)

请注意,通用类型必须在编译时才知道。您可以在运行时存储Type Class的实例,但不能使用它来代替通用参数。

您创建了两种类型:IFooIFoo<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的所有必要信息。