流畅 API 的类型推理

本文关键字:推理 类型 API 流畅 | 更新日期: 2023-09-27 18:24:41

>我有以下扩展方法:

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);

由于我实际上不仅在Foo()FooBar()之间Bar(),而且方法链很长,因此我将有巨大的额外实施成本。

有没有办法避免这个问题并"神奇地"转发TResult泛型参数?

编辑:

而不会丢失类型推断!

流畅 API 的类型推理

假设您能够从IFoo<TResult>转到IFoo并且您的方法链不关心TResult您可以通过将用法更改为以下内容来保存一些实现:

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

去掉没有类型参数的 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
    }
}

C# 中的流利接口依赖于通过每个.传递的类型(显式或隐式(。如您所描述的,如果您丢失了类型信息,则无法将其找回。

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

  • 当然,如果你的一些.Bar实际上像.First.SingleOrDefault那么无论如何都不应该跟着.FooBar(至少不是直接的(。

请注意,泛型类型必须在编译时知道。可以在运行时存储 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 的所有必需信息。