流畅 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
泛型参数?
编辑:
而不会丢失类型推断!
假设您能够从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 的实例,但不能使用它代替泛型参数。
您已经创建了两种类型: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 的所有必需信息。