【问题标题】:Type inference for fluent API流式 API 的类型推断
【发布时间】:2013-07-24 18:55:36
【问题描述】:

我有以下扩展方法:

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 泛型参数?

编辑:

不会丢失类型推断!

【问题讨论】:

  • 您的 fluent API 中是否有任何理由使用非泛型方法?如果没有,请完全抛弃它们,转而使用通用的。
  • 你为什么没有public static IBar&lt;T&gt; Bar (this IFoo&lt;T&gt; foo);
  • @Chris:不幸的是,我应该在 Foo(Action) 中返回什么?
  • @supercat:用户不需要,为什么要实现呢?不幸的是,如果我想使用类型推断,我必须实现它......现在的问题是:如何克服这个缺陷?
  • 我根本不会称之为“缺陷”。我认为您最好的选择是要么实现所有方法,要么创建一个包装通用构建器,其中 TResult 在类级别定义一次。 (我想这样做意味着放弃扩展方法的使用,并且仍然需要你实现这些方法)我说只是咬紧牙关,做正确的事情并避免“魔术”。编辑:对不起,我想我现在完全理解这个问题了。您需要在非通用调用之间链接通用信息。是的,尝试制作一个单独的构建器类。

标签: c# generics fluent fluent-interface


【解决方案1】:

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

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

【讨论】:

  • 可以这样做,但是噪音很大。尽管有许多不同的类似 Bar() 的方法,但大多数时候用户将只调用一个,这将导致 .Bars(foo => foo.Bar()) 无济于事:/ 感谢您的尝试-> 赞成。
【解决方案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
    }
}

【讨论】:

    【解决方案3】:

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

    您唯一的选择是在表达式中包含分支,如 Shawn 的回答中所述,或者您必须只包含 IBar&lt;TResult&gt; Bar&lt;TResult&gt; (this IFoo&lt;TResult&gt; foo),以便始终传递所需的类型信息。

    • 当然,如果您的某些.Bars 实际上类似于.First.SingleOrDefault,则无论如何都不应该跟在.FooBar 后面(至少不能直接跟在后面)。

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-05-15
        • 2018-06-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-08
        • 1970-01-01
        相关资源
        最近更新 更多