【问题标题】:Using a lambda expression to avoid using a "magic string" to specify a property使用 lambda 表达式来避免使用“魔术字符串”来指定属性
【发布时间】:2011-03-20 20:01:14
【问题描述】:

我正在编写一个服务来获取特定类型的对象集合和output its primitive, string, and DateTime typesCSV Format 中的字符串。我有以下两个陈述。我发现基于 lambda 的版本要干净得多。

魔串版本

string csv = new ToCsvService<DateTime>(objs)
    .Exclude("Minute")
    .ChangeName("Millisecond", "Milli")
    .Format("Date", "d")
    .ToCsv();

对比Lambda 版本

string csv = new ToCsvService<DateTime>(objs)
    .Exclude(p => p.Minute)
    .ChangeName(p => p.Millisecond, "Milli")
    .Format(p => p.Date, "d")
    .ToCsv();

根据 Jon Skeet 的建议,所有 lambda 方法共享一个相似的方法签名

public IToCsvService<T> Exclude<TResult>(
        Expression<Func<T, TResult>> expression)

然后我将expression.Body 传递给FindMemberExpression。我从nhlambdaextensions project 改编了FindMemberExpression method of ExpressionProcessor.cs 的代码。我非常相似的FindMemberExpression 版本如下:

private string FindMemberExpression(Expression expression)
{
    if (expression is MemberExpression)
    {
        MemberExpression memberExpression = (MemberExpression)expression;

        if (memberExpression.Expression.NodeType == ExpressionType.MemberAccess
            || memberExpression.Expression.NodeType == ExpressionType.Call)
        {
            if (memberExpression.Member.DeclaringType.IsGenericType
                && memberExpression.Member.DeclaringType
                .GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
            {
                if ("Value".Equals(memberExpression.Member.Name))
                {
                    return FindMemberExpression(memberExpression.Expression);
                }

                return String.Format("{0}.{1}",
                    FindMemberExpression(memberExpression.Expression),
                    memberExpression.Member.Name);
            }
        }
        else
        {
            return memberExpression.Member.Name;
        }
    }

    throw new Exception("Could not determine member from "
        + expression.ToString());
}

我正在测试FindMemberExpression 中的足够案例?考虑到我的用例,我正在做的事情是不是有点矫枉过正?

【问题讨论】:

    标签: c# csv lambda magic-string


    【解决方案1】:

    编辑:使这更简单的核心是将方法的签名更改为在结果类型中也是通用的:

    public IToCsvService<TSource> Exclude<TResult>(
        Expression<Func<TSource, TResult>> expression)
    

    这样您就不会得到转换表达式,因为不需要转换。例如,由于类型推断,p =&gt; p.Minute 最终将自动成为Expression&lt;Func&lt;DateTime, int&gt;&gt;


    在我看来,这似乎有点矫枉过正,因为目前您所需要的只是一个属性 - 至少,这就是您的示例所显示的全部内容。

    为什么不从识别一个属性开始,然后如果你需要来扩展它?

    编辑:这是一个简短但完整的示例,不显示任何转换:

    using System;
    using System.Linq.Expressions;
    
    class Test
    {
        static void Main()
        {
            Expression<Func<DateTime, int>> dt = p => p.Minute;
            Console.WriteLine(dt);
        }
    }
    

    但是,如果您将表达式类型更改为 Expression&lt;Func&lt;DateTime, long&gt;&gt;,它确实显示 Convert(...) 位。我怀疑您需要更改 Exclude (等)方法的签名。

    【讨论】:

    • OT:约瑟;从 200k 开始大约需要 4 天?
    • 为了识别一个属性,我只使用了UnaryExpression 路径和MemberExpression 路径的简化版本。有没有比我概述的更干净的机制?
    • @Marc: 确实 - 3 如果我真的很幸运 :)
    • @ahsteele:如果您只识别属性,为什么需要 UnaryExpression 路径?这不是只是 MemberAccess 吗?你真的需要处理演员阵容吗?
    • 我想知道哪个会先发生; 100k C# 问题或 200k Jon Skeet 积分;p
    【解决方案2】:

    您是否有任何计划让它更灵活,或者这就是它需要做的全部?

    理想情况下,您应该拥有最简单的代码来完成您需要完成的工作,这样您就可以减少可能出错的事情的数量。

    如果您这样做是为了证明概念,并且知道稍后您将需要 lambda 表达式,那么将它们保留在那里是有意义的,但是,如果这是最终产品,那么前者更易于阅读,如果其他人需要对代码进行更改,则不太可能引起混淆。

    【讨论】:

    • 我完全是为了可读性,但类型安全难道不值得使用 lambda 表达式的“成本”吗?
    • @ahsteele - 无论如何,您都将其输出到字符串,因此,除非您对这些值执行某些操作,否则除了写入文件之外,类型安全对您没有任何作用。此外,如果您担心类型安全,除了增加 lambda 表达式的复杂性之外,还有其他方法可以实现。保持尽可能简单是这里的重要部分。
    • 同意,但我说的是我正在处理的类的类型安全。出于本示例的目的,我使用了 DateTime,但如果我使用的是我控制并更改了属性名称的值类型,则 lambda 表达式将对此有所帮助。而魔术弦版本会让我一无所知,直到我后来发现了这个问题。我倾向于做可能可行的最简单的事情,但我想知道这是否是这里的“正确”答案。我错过了什么吗?
    • @ahstelle - 你预见到不需要使用 DateTime 吗?这才是真正的问题。不要为某天可能需要的东西而设计,这会导致问题。我在回答的第一句话就问了这个问题。
    • 我明白你在说什么。这将用于各种值类型。假设我做的“正确”,我什至想在 CodePlex 或 Google Code 上制作最终产品 OSS。
    猜你喜欢
    • 1970-01-01
    • 2012-06-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-21
    • 1970-01-01
    相关资源
    最近更新 更多