Hello World
Spiga

为URL生成设计流畅接口(Fluent Interface)

2009-11-03 09:43 by 老赵, 18815 visits

昨天我比较了三种URL生成方式的性能,并对结果进行了分析。从结果中我们得知使用Lambda表达式生成URL的性能最差,而且差到了难以接受的地步。经过分析,我们发现其中光“构造表达式树”这个阶段就占了接近30%的开销。虽然表达式树的节点是有些多,但是.NET中创建对象其实非常快,我实在没想到它会占这么高的比例。因此,我们需要这种做法进行方向性的调整,减少对象创建的数目。

但是,既然我们要尽可能地保留“静态检查”的优势,因此这里的关键是如何设计出既美观好用,又高效的做法。例如,我在上一篇文章最后留下的做法是这样的:

// API签名 
public static string Action(this UrlHelper helper, Expression template, params object[] args)
 
// 使用方式 
private readonly static Expression<...> ToPostTemplate = (c, blog, post) => c.Post(blog, post);
public static string ToPost(this UrlHelper helper, Blog blog, Post post)
{
    return helper.Action(ToPostTemplate, blog, post);
}

我要求为每个Action都指定一个表达式“模板”,它是一个单例,既可以作为解析,又可用于缓存。它也不会创建额外对象,因此可以缓解性能问题。但是,它的缺点也很明显:

  • 必须单独创建一个表达式,因此使用比较麻烦(无法直接在视图中调用)。
  • 表达式模板的签名非常麻烦,无法利用C#编译器的类型推导能力。
  • 使用params object[]指定Action的参数,仍有些“弱类型”的意味。

因此,这个方式其实并不理想。不过,在文章的评论中,Ivony...老大给了我一些提示,然后再讨论中,我们总结出了一个似乎不错的API。这个API首先利用了C#编译器的一个特性:可以将一个方法直接转化为相同签名的委托对象,而不需要使用new进行显式创建。例如:

HomeController controller = new HomeController();
Func<Blog, Post, ActionResult> action = controller.Post;

如第2行代码,其完整的写法应该使用new关键字,配合委托的类型,把方法包装为一个委托。但是,从C# 2.0起编译器就允许我们使用省略的写法。于是接下来,我们便要设法利用C# 3.0中那微弱的类型推导能力进行API设计了。从以前的一次讨论中,我们了解了C# 3.0编译器类型推导的一些“盲点”,例如:一个泛型方法只有通过传入的参数,才能确定泛型参数的具体类型。它使得以下代码无法编译通过:

// 定义
static void Do<T1, T2>(Func<T1, T2, ActionResult> action)

// 调用(类型推导失败)
Do(controller.Post);

除非通过其他两个参数来明确T1和T2的类型:

// 方法定义
static void Do<T1, T2>(Func<T1, T2, ActionResult> action, T1 arg1, T2 arg2)

// 调用(编译成功)
Post post = null;
Blog blog = null;
HomeController controller = new HomeController();
Do(controller.Post, blog, post);

可惜委托的返回值还是必须指明ActionResult类型,它无法推导出来。不过,这似乎也基本满足我们的需求。那么,我们又如何为Do方法提供controller的类型信息呢?要知道这样的API是行不通的:

public static string Action<TController, T1, T2>(this UrlHelper helper, ...)

因为C#编译器不允许在方法调用时指定部分参数(如单独指定TController而让T1,T2由类型推导获得),因此我们必须将Controller类型的指定工作,与剩下的泛型参数分开:

public static class UrlHelperExtensions
{
    public static ActionOf<TController> Of<TController>(this UrlHelper helper) where TController : new()
    {
        return new ActionOf<TController>(helper);
    }
}

public class ActionOf<TController>
{
    public ActionOf(UrlHelper urlHelper)
    {
        this.m_urlHelper = urlHelper;
    }

    public UrlHelper m_urlHelper;
}

在ActionOf类中已经明确了TController泛型类型,它只要负责推导后续的参数即可,因此我们定义出这样的方法:

public string Action<T1, T2>(Func<TController, Func<T1, T2, ActionResult>> action, T1 arg1, T2 arg2)

请注意现在Action方法第一个参数的类型:这是一个委托对象,接受TController类型作为参数,返回另一个委托对象——它不需要使用new进行显式创建。这个委托对象返回ActionResult类型,并接受T1,T2两个参数——这两个参数的类型又可以由Action方法的后续参数确定下来。因此,最后的调用方法大概是这样的:

Url.Of<HomeController>().Action(c => c.Post, blog, post)

这行代码可读性也不错,我们可以这样理解:“URL of HomeController’s action ‘Post’ with parameter ‘blog’ & ‘post’...”,因此它也可以算是一个流畅接口(Fluent Interface)。这个调用方式在我看来还是挺美观的,而且有明确的静态类型检查,属于比较理想的API。至于在Action方法内部,我们可以通过传入一个TController类型的对象来得到一个委托,这个委托对象的Method属性便是那个Action的MethodInfo——至于它的参数,便是blog和post了:

public class ActionOf<TController> where TController : new()
{    
    private static TController prototype = new TController();

    public string Action<T1, T2>(Func<TController, Func<T1, T2, ActionResult>> action, T1 arg1, T2 arg2)
    {
        return Action(action(prototype).Method, arg1, arg2);
    }

    public static string Action(MethodInfo methodInfo, params object[] args) { ... }
}

在这段代码中,我要求TController类型有个默认的构造函数,这样便于我们构造一个TController来“获取委托”,并得到它的Method信息。在实际使用过程中,我们不能强求每个Controller类型都满足这个条件,因此我打算使用Emit来动态生成TController的子类。使用Emit的好处在于,我们生成的动态类型可以绕开C#编译器的限制,不调用任何基类的构造函数,这样可以创建一个无用的对象而不会触及基类的任何逻辑,恰好满足我们的需求。

当然,这种流畅接口和Lambda表达式相比还是有些缺点,如:

  • 获得IDE的智能提示效果不佳。
  • 需要为参数数目不同的Action方法准备不同的重载。
  • 对类型要求严格,而构造Lambda表达式可以如普通C#代码那样的“宽松”

关于最后一点可能值得用代码示例来说明。如这样的Action方法:

public ActionResult Detail(long id) { ... }

而构造URL时:

int id = 0;
helper.Action<HomeController>(c => c.Detail(id));  // Lambda Expression
helper.Of<HomeController>().Action(c => c.Detail, (long)id); // Fluent Interface

在流畅接口中将id强制转型为long的操作是不可以省略的,因为ActionOf<HomeController>.Action方法需要根据泛型类型来推断出c.Detail的签名。如果传入一个int类型的id,则编译器便会告知我们“c.Detail不符合Func<int, ActionResult>类型”,进而编译失败。而使用Lambda表达式时,C#编译器会自动对id进行“提升(Lift)”操作,把int转化为long。不过这点在实际使用过程中应该不会成为问题。

使用流畅接口生成URL时还是会创建一些对象,例如ActionOf对象,action委托对象、调用action后得到的另一个委托对象等等,可能还需要包括公用的Action(MethodInfo, params object[])方法所需要的对象数组。不过对象数量还是要比Lambda表达式少一些,而且不像Expression类型的那些工厂方法包含一些逻辑,因此在性能是有所提高的。这里我进行了一个简单的性能测试,得到的结果为:

Lambda Expression
        Time Elapsed:   9,353ms
        CPU Cycles:     20,339,339,141
        Gen 0:          373
        Gen 1:          0
        Gen 2:          0

Fluent Interface
        Time Elapsed:   3,041ms
        CPU Cycles:     6,614,088,228
        Gen 0:          97
        Gen 1:          0
        Gen 2:          0

可见,使用流畅接口的方式,在构造URL的第1阶段(构造对象)可以得到超过2/3的性能提升,而如果参数数量多的话差距应该会更为明显。

您觉得这个方式如何?可以给出您的看法吗?

相关文章

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

55 条回复

  1. Leven
    *.*.*.*
    链接

    Leven 2009-11-03 09:44:00

    先占沙发再看

  2. 老赵
    admin
    链接

    老赵 2009-11-03 09:49:00

    @Leven
    沙发没钱的,还是先看吧。

  3. 今天外文网站上不去?[未注册用户]
    *.*.*.*
    链接

    今天外文网站上不去?[未注册用户] 2009-11-03 10:09:00

    谁知道原因啊?
    电信网络

  4. 今天外文网站上不去?[未注册用户]
    *.*.*.*
    链接

    今天外文网站上不去?[未注册用户] 2009-11-03 10:10:00

    百度首页也无法打开。

  5. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-11-03 10:11:00

    我想这个最终解决方案有三个“奇技淫巧”是非常重要和值得借鉴的。(很惭愧我只提供了一个)

    首先是利用参数类型推导委托类型,即:
    public void Invoke<T1, T2, T3 ...>( Action<T1, T2, T3 ...> method, T1 arg1, T2 arg2, T3 arg3 ... )

    其实在我看来这不够优美,其实我最喜欢的还是:
    public Action<T1, T2, T3 ...> MethodWrapper<T1, T2, T3 ...>( Action<T1, T2, T3 ...> method)

    问题是这种形式在C#的类型推演中是做不到的。
    这种形式如果成立,就是真正的从方法推导参数类型,则类型绑定的时候就与常规的函数调用毫无二致了,不会出现int、long的转换问题。

    显然这种手法不仅仅可以用于这个场景。这种方法可以将方法的调用延迟,不论是远程调用还是植入自己的逻辑,这种手法都是可以考虑的(当然你也可以选择表达式树)。


    其次是提取泛型类型参数单独声明,即老赵发明的Of方法,Of方法可以将泛型方法的部分类型参数独立出来显示声明。老赵在这里是用来解决TController的问题,而事实上这种手法可以推广到任何场景。例如解决上面的手法的方法返回值的问题。


    最后一个手法就是老赵说的利用Emit创建类型伪实例。虽然我觉得这个手法的使用面可能要窄一些,但能想出用Emit来干这种“坏事”,真是令人佩服。因为使用面不广,我就不深入探讨了。这种手法主要是给大家提个醒,思路放宽点,眼界放广点,总是在不经意间,那些“丑陋的”、“邪恶的”东西就能帮你解决大问题。

  6. 垃圾呆
    *.*.*.*
    链接

    垃圾呆 2009-11-03 10:12:00

    问个纠结的问题~
    在有些时候有些方法是对传入对象的复制操作,而当这些对象不是值类型的时候,也就没必要用ref 之类修饰符,但用VS重构操作提取这些方法的时候貌似都会直接加上ref修饰符?感觉这种做法除了明显一点,容易让别人看懂这函数的作用,感觉就~
    所以不知道这有没必要?

  7. 装配脑袋
    *.*.*.*
    链接

    装配脑袋 2009-11-03 11:58:00


    @Jeffrey Zhao
    使用Of做方法名是不对的,在VB里使用语法会变成X.Of(Of T)不考虑其他语言的方案不是真正优美的方案

    @Ivony...
    你说的是这种签名吗。。

    Module Module1
    
        Sub Main()
            Dim a As Action(Of String) = AddressOf Console.WriteLine
    
            Dim i = F(a)("asdf")
    
        End Sub
    
        'public Action<T1, T2, T3 ...> MethodWrapper<T1, T2, T3 ...>( Action<T1, T2, T3 ...> method)
    
        <Extension()> _
        Public Function F(Of T)(ByVal mthod As Action(Of T)) As Func(Of T, Integer)
            Return Function(x) 1
        End Function
    End Module
    

  8. 老赵
    admin
    链接

    老赵 2009-11-03 12:34:00

    @装配脑袋
    不懂VB……其实不考虑也没什么吧,只是解决方案不能跨语言了,这个也很正常呀,例如为F#设计的API也没法用在C#上,嘿嘿。
    好吧,VB可能还是要考虑的,那么用什么方法名比较好呢?

  9. 装配脑袋
    *.*.*.*
    链接

    装配脑袋 2009-11-03 13:11:00

    在.NET上如果没有实现跨语言的抽象,这种抽象就绝对无法流行和被别人采用。例如C++/CLI template。

    另外我认为这种API可接受性非常值得商榷。我现在还是推崇用语言元素做原本职责的事情,除非是自己玩。这种Url.Of<HomeController>().Action(c => c.Post, blog, post)的语法,真正给别人使用的时候,没有人会从方法签名或类的定义中快速看出它的使用方法。

  10. 装配脑袋
    *.*.*.*
    链接

    装配脑袋 2009-11-03 13:19:00

    另外你也应该能从我的代码里看出,在VB里,从方法名获得委托需要AddressOf运算符,你的代码就会变成Url.Of(Of HomeController).Action(Function(c)AddressOf c.Post, blog, post)。这其实代表了委托和方法语义上的差异。用这种语法,将造成滥用语义的倾向。
    思考一下自己的代码在别的语言里长什么样对API设计来说是极其重要的。

  11. CoolCode
    *.*.*.*
    链接

    CoolCode 2009-11-03 13:20:00

    用On吧,如何?至少暂时没有看到有语言用这个做关键字,嘿嘿

  12. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-11-03 13:23:00

    装配脑袋:
    @Jeffrey Zhao
    使用Of做方法名是不对的,在VB里使用语法会变成X.Of(Of T)不考虑其他语言的方案不是真正优美的方案

    @Ivony...
    你说的是这种签名吗。。
    [code=vbnet]
    Module Module1

    Sub Main()
    Dim a As Action(Of String) = AddressOf Console.WriteLine

    Dim i = F(a)("asdf")

    End Sub

    'public Action<T1, T2, T3 ...> MethodWrapper<T...




    不是,返回值不是Func而是Action

    那个<Extension()>是表示扩展方法么?

    其实我的意思是如果有这样的方法:


    public Action<T1, T2, T3 ...> F( Action<T1, T2, T3 ...> method )
    {
    return ( arg1, arg2, arg3 ... ) =>
    new ActionInfo( method, arg1, arg2, arg3 );
    }

    这样就可以实现这样的效果:

    ActionInfo action = F( DoSomething )( 1, 2, 3 );

    看起来和DoSomething( 1, 2, 3 )差不多,实际效果就是把这个方法调用的信息(参数值、方法)保存下来,而不去调用这个方法。

    可是做不到,而且做得到的话,语义上来说:
    Url.Action( F(c.Post)( blog, post ) );

    也更舒服不是么?

  13. 老赵
    admin
    链接

    老赵 2009-11-03 13:23:00

    @垃圾呆
    我没听懂……

  14. 装配脑袋
    *.*.*.*
    链接

    装配脑袋 2009-11-03 13:26:00

    我觉得这条路彻底走歪了。从方法名获得委托,仅仅在用户明确知道是委托的上下文才有意义。你们不能滥用这个语法。

  15. 老赵
    admin
    链接

    老赵 2009-11-03 13:32:00

    @装配脑袋
    其实我觉得API还是针对特定语言来说可能比较合适,否则的话,就算以前的Lambda表达式写法:
    Url.Action<HomeController>(c => c.Post(blog, post))放在VB里写起来还是麻烦。
    但是这种方式,微软也很推崇啊,在V2里也有类似的API(不过不是做同样的事情)。

    如果思考非C#的语法的话,很多东西就不好办了,比如Moq,比如F#的API也不会考虑C#的使用方式,语言特性不同么……
    我现在就好比是在为80%占有率的语言设计API,而只能忽视20%了,这是准备中的,除非有更好的设计,否则只能满足这80%了……

  16. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-11-03 13:34:00

    装配脑袋:我觉得这条路彻底走歪了。从方法名获得委托,仅仅在用户明确知道是委托的上下文才有意义。你们不能滥用这个语法。




    这个没弄明白。

    委托不仅仅是方法的调用包装(透过委托可以调用方法),同样也可以是方法的信息包装(透过委托找到方法)。我们一般用前者,但不见得后者就歪了呀。

  17. 老赵
    admin
    链接

    老赵 2009-11-03 13:35:00

    @装配脑袋
    至于是不是可以从方法的签名中得出使用方式,这个我也不太在意。尤其是在视图中,我一般都把这些东西当作是DSL来看待,所以我会设计出“url of HomeController's action Post with...”这种语义的API。
    而使用的时候只要记住“用法”就行了,不关心“签名”究竟如何,就像使用Fix来生成递归内容,签名实在看不出来(http://www.cnblogs.com/JeffreyZhao/archive/2009/09/27/rendering-tree-like-structure-recursively.html

    的确如果可以从签名看出来那自然最为理想,但是在DSL面前,这点还是让步吧,比如在FsTest里:
    1 + 1 |> should be 2
    1 + 2 |> should not be 5

    这种东西,你看should方法,be方法,not方法……都是搞不懂该怎么用的。
    但就是为了让别人看明白写出来的代码,让别人知道怎么写,呵呵。

  18. 装配脑袋
    *.*.*.*
    链接

    装配脑袋 2009-11-03 13:36:00

    Url.Action<HomeController>(c => c.Post(blog, post))

    这种API本来就是稍显拙劣的设计。泛型方法应该尽量避免显示指定类型参数。但不管怎么说,这个起码还能从签名看出用法。

    这里不应该偷换概念。难看不难看,只是你的看法而已。我说的都是切切实实的害处。你滥用了语义,用户没法从定义看出用法,这才是最根本的问题。如果.NET加这种API进去,那PM一定是没把好关。

  19. 老赵
    admin
    链接

    老赵 2009-11-03 13:39:00

    装配脑袋:我觉得这条路彻底走歪了。从方法名获得委托,仅仅在用户明确知道是委托的上下文才有意义。你们不能滥用这个语法。


    但是我觉得这个API很好用也很好认啊,我其实并不在意用户是否意识到自己在创建委托,我追求的只是个表达方式,以及它的效果(比如静态检查等等)吧。

  20. 装配脑袋
    *.*.*.*
    链接

    装配脑袋 2009-11-03 13:42:00

    用户不会想到这里是要写c => c.Detail,你没法限制用户第一层必须使用Lambda表达式,所以用户不可能思考到诸如Func<..,Func<>>是做这个目的的。

    至于DSL,我觉得大部分尝试真的是玩具。论坛上好多人也热衷于折腾。但如果你想让你的类库有实用性,做API的时候正的必须要好好考虑所用语法的语义。

  21. 老赵
    admin
    链接

    老赵 2009-11-03 13:42:00

    @装配脑袋
    Url.Action<HomeController>(c => c.Post(blog, post))
    这个API不拙劣吧,因为总归要让用户指定是HomeController的Post这个Action方法的,不能省略任何信息的,所以我觉得很合适啊。
    其实,如果有别的办法,我也不会设计出这样的API,但是既然没有别的办法……只能退而求其次,设计出DSL风格的API,即便用户无法从签名看出用法吧。

  22. 老赵
    admin
    链接

    老赵 2009-11-03 13:44:00

    装配脑袋:
    用户不会想到这里是要写c => c.Detail,你没法限制用户第一层必须使用Lambda表达式,所以用户不可能思考到诸如Func<..,Func<>>是做这个目的的。
    至于DSL,我觉得大部分尝试真的是玩具。论坛上好多人也热衷于折腾。但如果你想让你的类库有实用性,做API的时候正的必须要好好考虑所用语法的语义。


    是没法限制,这也是DSL的普遍缺点,所以DSL都是“告诉用户怎么用”,不需要让用户自己根据签名去识别。
    DSL肯定也不是万能的,所以也就这一个地方尝试一下而已,比如:
    <a href="<%= Url.Of<HomeController>().Action(c => c.Post, blog, post) %>">Hello World</a>
    平时写代码设计API自然不会这样。
    总之,如果有别的办法,那么就不用这个,否则我觉得这种用法还是很不错的,呵呵……

  23. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-11-03 13:46:00

    装配脑袋:
    Url.Action<HomeController>(c => c.Post(blog, post))

    这种API本来就是稍显拙劣的设计。泛型方法应该尽量避免显示指定类型参数。但不管怎么说,这个起码还能从签名看出用法。

    这里不应该偷换概念。难看不难看,只是你的看法而已。我说的都是切切实实的害处。你滥用了语义,用户没法从定义看出用法,这才是最根本的问题。如果.NET加这种API进去,那PM一定是没把好关。




    其实我也认为这样使用lambda是拙劣的,至少他是违背了lambda的语义。但实际情况是什么呢?实际情况是C#并没有提供一种很好的语法来让我们描述一个方法调用(委托只能描述方法)。这个时候各路神仙就只好用现有的这些东西尽可能的去拙劣的实现了。


    比如说,如果C#可以提供一种语法,像下面这样
    ActionInfo action = new ActionInfo( c.Post( blog, post ) );

    我想我们都很乐意于这样写:
    Url.Action( new ActionInfo( c.Post( blog, post ) );

    但现在的问题是没有。

  24. 装配脑袋
    *.*.*.*
    链接

    装配脑袋 2009-11-03 13:49:00

    你觉得这这么写真的优美吗?用奇技淫巧来写代码的时代已经过去了,C++的同僚们都开始清醒了……

  25. 装配脑袋
    *.*.*.*
    链接

    装配脑袋 2009-11-03 13:54:00

    退一步海阔天空啊,我没觉得字符串拼接法有多差劲。

  26. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-11-03 13:56:00

    装配脑袋:你觉得这这么写真的优美吗?用奇技淫巧来写代码的时代已经过去了,C++的同僚们都开始清醒了……




    老实说我是没觉得这样优美的……

    不过我觉得这样也不算是特别的那啥。我明白你的意思了,如果这个东西作为API提供的确是不太合适的。的确是不能“自然”的直接悟出使用方式。


    好吧,我的确没用API的高度去约束它。但实际上现在.NET的一些API的风格也在变,也有一些不是那么严谨的开始出现。


    我觉得这个度还是很难去把握。也同意你的观点。

  27. CoolCode
    *.*.*.*
    链接

    CoolCode 2009-11-03 13:57:00

    感觉在看现场直播,嘻嘻

  28. 老赵
    admin
    链接

    老赵 2009-11-03 13:57:00

    @装配脑袋
    我觉得挺优美的啊,至少读的很通顺——不过不如Lambda表达式的……
    奇技淫巧也不都是坏事呀,要看目的是什么。
    如果是为了缩短代码,写成一行等等,那意义不大。
    我这是的目标是代码可读性好,且有静态检查……
    可能是不太容易写吧,但我觉得代码毕竟是读的多,而且这写法用用就习惯了……

  29. 老赵
    admin
    链接

    老赵 2009-11-03 13:59:00

    装配脑袋:退一步海阔天空啊,我没觉得字符串拼接法有多差劲。


    嗯嗯,的确很多时候字符串拼接也够了。
    可能是我更追求一些东西吧,比如把相关代码归在一处啊,不重复定义啊等等……

  30. 装配脑袋
    *.*.*.*
    链接

    装配脑袋 2009-11-03 14:00:00

    读着很通顺并不等于很容易地理解其行为。用户一般视方法为动作,每一步应该有每一步的语义。

    Url.Of<????>...这一步你想让用户看出什么语义呢?将Url转化为???类型吗?

    如果想让这个稍微有点语义的话,我看只能设计成这样:

    Url.CreateForType<???>....

  31. 老赵
    admin
    链接

    老赵 2009-11-03 14:06:00

    @装配脑袋
    我是看整句的:
    url of HomeController's action Post with paramters blog, post...
    其中只有“'s”和“with parameters”是我加上去的,这也是我选择用Of作为方法名的原因。

    就像FsTest里:
    1 + 1 |> should be 2
    它是作为一句话看,不是should做一件事,be又做了一件事。

    还有比如Fluent NHibernate里:
    Reference<UserDetail>().Not.LazyLoad();
    我觉得也是从整句考虑,而不是一个方法一个步骤。

  32. 老赵
    admin
    链接

    老赵 2009-11-03 14:08:00

    @装配脑袋
    不用Of的话,用To如何?似乎也不错,而且兼顾VB,或者For?

    Url.To<HomeController>().Action(c => c.Post, blog, post);
    Url.For<HomeController>().Action(c => c.Post, blog, post);

    可能To比较合适,因为比如在Controller里是这样的:
    public ActionResult Do()
    {
        ...
        return RedirectTo<HomeController>().Action(c => c.Post, blog, post);
    }
    这样API也统一了,我是指都是xxx to...

  33. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-11-03 14:19:00

    我考虑的是By。


    嗯,其实我觉得应该说只是有分歧没矛盾。

    你要我说,我欣赏老赵这样的语法么?老实说我不欣赏,虽然部分方法虽然是我提出的,但这种语法要我接受我还需要一些时间,我在考虑这个方法的时候,也过分的追求F(c.Post)( blog, post )而不是F( c.Post, blog, post )的形式,因为我觉得前者才像是函数调用,后者看起来就不像是函数调用了,这样会造成一些阅读障碍。

    对前种形式过分的追求还使得我竟然没发现后种形式是成立的(真是个低级错误)。

    至于老赵所用的这样两个方法的配合,你要我说,我真的说现阶段在API我还是不能完全认同。这也是前阵子鹤冲天给出Switch.Case扩展方法的时候我不吱声的原因。但我也觉得,虽然现在还难以接受,却也真的不是有什么强有力的根据说这种方式有多么不可取。诚然,这种方法如果按照传统的从方法签名参数含义来阐述,基本上100个人99个不知道怎么用。不给Demo几乎没办法正确的理解使用方式。

    但是尽管我觉得别扭,却也不得不承认这样的方案不错。我并没有放弃语义更明晰的方案的探索和寻找,却也真找不出什么特别有份量的理由否决这种方式。

  34. 老赵
    admin
    链接

    老赵 2009-11-03 14:28:00

    @Ivony...
    其实就是你的F(c.Post)(blog, post)让我觉得括号有点多,不太好看,进而想到这其实就是函数的partial application,展开后就变我的样子了。
    还有就是,如果采用你的做法,我可能就会选择更Fluent一些的方式:
    Url.To<HomeController>().Action(c => c.Post).WithParameters(blog, post);
    但是还是同样道理没法这样搞……还有长了些……

  35. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-11-03 14:40:00

    Jeffrey Zhao:
    @Ivony...
    其实就是你的F(c.Post)(blog, post)让我觉得括号有点多,不太好看,进而想到这其实就是函数的partial application,展开后就变我的样子了。
    还有就是,如果采用你的做法,我可能就会选择更Fluent一些的方式:
    Url.To<HomeController>().Action(c => c.Post).WithParameters(blog, post);
    但是还是同样道理没法这样搞……还有长了些……




    所以这种事情是个见仁见智的问题,有不同意见最好还是“憋着”o(∩_∩)o 。。。。。


    接受是需要一些时间的,老实说我是比较传统的程序员。我坦白第一次看到XElement的构造函数的时候,我都觉得不是很舒服。这种事情怎么说呢,可以说现在双方的观点我都认同,所以说我觉得没有矛盾,只有分歧。从这个角度来说,A是合理的,从另一个角度来说,B是合理的。不存在一个角度A和B存在非此即彼的关系。

    哈哈,我很传统,我暂时是不太能认可连着点的。
    就像我给出的方案中甚至是Action( C<HomeController>().Post ... )一样,宁可用一个无谓的函数C来创建伪对象。。。。因为我觉得一个函数应该可以独立的完成某件事情,就算加工的是个半成品。函数与函数之间不应有强耦合关系,不应该A函数的结果只能调用B函数,这时候应该将A、B合并。

    不说了,反正是个见仁见智的问题。

  36. 装配脑袋
    *.*.*.*
    链接

    装配脑袋 2009-11-03 14:48:00

    我觉得To啊For啊不是最成问题的地方,最成问题的地方就是c => c.Detail这个语法非常不适合用在这里。

  37. 装配脑袋
    *.*.*.*
    链接

    装配脑袋 2009-11-03 14:49:00

    @Ivony...
    “函数与函数之间不应有强耦合关系,不应该A函数的结果只能调用B函数,这时候应该将A、B合并。”

    我非常同意这种说法。。。

  38. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-11-03 14:53:00

    装配脑袋:我觉得To啊For啊不是最成问题的地方,最成问题的地方就是c => c.Detail这个语法非常不适合用在这里。




    那不妨来评价下我的想法吧,传统型的:

    Url.Action( Helper.Dummy<HomeController>().Post, blog, post)


    要我设计接口的话,我就会设计成这样,但我还是觉得难看,我还在探索更好的。

    但我也不反对老赵的,因为看起来也不错。只是我可能还需要花时间去接受。

  39. CoolCode
    *.*.*.*
    链接

    CoolCode 2009-11-03 14:56:00

    我也有个想法: ActionHelper<DemoController>.Action(c => c.Post, "hello", (long)123);
    这样如何?

  40. 装配脑袋
    *.*.*.*
    链接

    装配脑袋 2009-11-03 15:00:00

    如果用c => c.Detail这个API就真的成了少数人的玩物了。其实ExpressionTree的语法就是最满足要求的了,只是有性能问题罢了。
    要是我设计,这时候只能选择字符串拼接了。

  41. 老赵
    admin
    链接

    老赵 2009-11-03 15:03:00

    @CoolCode
    UrlHepler一定要传进去的,因为要它的RequestContext和RouteCollection。

  42. CoolCode
    *.*.*.*
    链接

    CoolCode 2009-11-03 15:06:00

    @Jeffrey Zhao
    优美与性能有时真的有点像鱼与熊掌啊~

  43. 垃圾呆
    *.*.*.*
    链接

    垃圾呆 2009-11-03 15:50:00

    @Jeffrey Zhao
    其实问题很简单,就是不知道怎么样的做法是比较好的,很尴尬
    例如有一个User 对象
    然后一个void SetDefault(User user,……)可能还有类似的对象
    由于User对象是引用传递,所以所有的修改都应该是传入那个对象修改
    但今天用VS的重构提取方法功能的时候 提取出类似方法 结果是
    void SetDefault(ref User user,……) 很多余的ref ~
    所以就像这种ref 是保留比较好还是除去,保留的话除了 看起来比较明显外没什么作用,所以又点纠结~不知道为啥用vs会自动加个ref 上去

  44. 装配脑袋
    *.*.*.*
    链接

    装配脑袋 2009-11-03 16:23:00

    @垃圾呆
    因为你这段代码里对user赋值了,比如有user = null之类的。。

  45. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-11-03 18:07:00

    CoolCode:
    @Jeffrey Zhao
    优美与性能有时真的有点像鱼与熊掌啊~




    其实优美每个人的理解也不一样,比如我和装配脑袋就会更倾向于函数必须是动词才是优美的,所以难以接受老赵这种只要函数是单词就可以了(这么解释能解释清楚么?)。其实我一开始也不知道脑袋在反对什么,讨论着就明白了。

    但是这种问题其实没有对错之分。。。。

  46. 老赵
    admin
    链接

    老赵 2009-11-03 19:07:00

    @Ivony...
    其实……要不是DSL,或者MVC的Action,我也使用动词作为方法的,呵呵。

  47. 垃圾呆
    *.*.*.*
    链接

    垃圾呆 2009-11-03 19:43:00

    @装配脑袋

    装配脑袋:
    @垃圾呆
    因为你这段代码里对user赋值了,比如有user = null之类的。。



    就算不是null也会丢个ref,应该是很没必要吧?.NET的编程经验不是很多,所以有些东西很容易纠结怎么写才是很好的

  48. 垃圾呆
    *.*.*.*
    链接

    垃圾呆 2009-11-03 21:01:00

    @装配脑袋
    呃 ~知道咋回事了 囧

  49. 小姿[未注册用户]
    *.*.*.*
    链接

    小姿[未注册用户] 2009-11-04 13:52:00

    你好 我想问一个关于Linq的问题,我在数据层类库项目中使用Linq To Sql,然后页面层调用,最后发布网站后,我的应用程序配置文件并没有配置连接字符串,连接字符串写入了类库程序集中,如果我需要改连接字符串,总不能每次重新编译吧 ,这个问题怎么解决呢

  50. cnblogswu[未注册用户]
    *.*.*.*
    链接

    cnblogswu[未注册用户] 2009-11-05 16:25:00

    @垃圾呆
    怎么事啊

  51. 大白
    *.*.*.*
    链接

    大白 2009-11-07 10:28:00

    老赵,给你发了几次的邮件,你都忙的死去活来的,无暇解答。
    我只好借你宝地一用,没棒法,谁叫你人气那么强:
    http://www.cnblogs.com/Dabai/archive/2009/11/07/1597811.html
    这个问题,要是有空,帮我看看。是不是可行。点击这里

  52. 老赵
    admin
    链接

    老赵 2009-11-07 10:52:00

    @大白
    最近我在北京,今天忙完最后一点,晚上我处理一下其他事情。:)

  53. 老赵
    admin
    链接

    老赵 2009-11-08 00:18:00

    @小姿
    这也太容易了,自己想想吧……

  54. DavidLv
    *.*.*.*
    链接

    DavidLv 2009-12-14 11:59:00

    我也觉得没必要做成这样吧。优化过度就成了为了设计而设计了

  55. 老赵
    admin
    链接

    老赵 2009-12-14 12:11:00

    @DavidLv
    这是有目的的优化,不是为了优化而优化吧……

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我