12

我有以下扩展方法:

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

4 回答 4

4

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

api.Foo(x => ReturnLong())
   .Bars(foo=>foo.Bar1() //where foo is an IFoo
                 .Bar2()
                 .Bar3()
                 ...
    )
   .FooBar(x => ...);
于 2013-07-24T20:10:22.043 回答
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
    }
}
于 2013-08-09T19:59:07.367 回答
1

C# 中的 fluent 接口依赖于通过每个.. 正如您所描述的,如果您丢失了类型信息,您将无法找回它。

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

  • 当然,如果您的某些.Bars 实际上是喜欢的.First,或者无论如何.SingleOrDefault都不应该跟随它们.FooBar(至少不是直接跟随)。
于 2013-08-06T13:08:14.453 回答
0

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

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

于 2013-08-09T09:14:54.490 回答