我有以下扩展方法:
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
通用参数?
修改:
不丢失类型推断!
答案 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的实例,但不能使用它来代替通用参数。
您创建了两种类型: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的所有必要信息。