【问题标题】:Better way to write this method using lambda expression and generics使用 lambda 表达式和泛型编写此方法的更好方法
【发布时间】:2011-08-31 13:25:56
【问题描述】:

我需要一种方法来从 IDataRecord 中检索数据,而无需使用烦人的“魔术字符串”来表示字段名称。 经过一番研究,我想出了这个扩展方法:

public static T2 ReadValue<T1, T2>(this IDataRecord record, Expression<Func<T1, T2>> expression)
{
    MemberExpression body = (MemberExpression)expression.Body;
    string fieldName = body.Member.Name;
    int ordinal = record.GetOrdinal(fieldName);
    return (T2)(record.IsDBNull(ordinal) ? default(T2) : record.GetValue(ordinal));
}

然后我就这样使用这个方法:

product.Name = record.ReadValue<Product, string>(p => p.Name);

还有其他方法可以简化这种方法吗?我喜欢这种行为,但不喜欢这种风格! :)

非常感谢你!

【问题讨论】:

  • 这个问题更适合Code Review
  • 对不起,我不知道代码审查。你能移动我的问题吗?
  • 我不认为隐藏你正在使用魔术字符串的事实,同时仍然使用魔术字符串,是对使用魔术字符串的改进。
  • 我在这里看不到任何魔术字符串。所有表达式完全支持重构。我错过了什么吗?
  • 没关系,我看错了。

标签: generics lambda c#-3.0 extension-methods


【解决方案1】:

问题是您可以从属性中推断出string 结果类型,但您仍然必须在类型参数中指定它,因为您还必须指定Product

处理这个问题的一个好方法是键入数据记录:

IDataRecord<Product> productRecord = ...;

string name = productRecord.ReadValue(p => p.Name);

这似乎是可行的,因为在您所暗示的 ORM 上下文中,您应该知道记录表示的数据类型。

困难的部分是上面代码中的...。它需要一些基础设施,但您只需编写一次,就可以在任何地方使用它。第一步是导出类型化的数据记录:

public interface IDataRecord<T> : IDataRecord
{
    TValue GetValue<TValue>(Expression<Func<T, TValue>> getter);
}

接下来,使用Decorator pattern 实现类型化数据记录(无聊但简单):

public class DataRecord<T> : IDataRecord<T>
{
    private readonly IDataRecord _untypedRecord;

    public DataRecord(IDataRecord untypedRecord)
    {
        _untypedRecord = untypedRecord;
    }

    public TValue GetValue<TValue>(Expression<Func<T, TValue>> getter)
    {
        ...the original code...
    }

    ...pass through all other members to the untyped record...
}

最后,添加一个从无类型到有类型记录的转换:

public static class TypedDataRecords
{
    public static IDataRecord<T> TypedAs<T>(this IDataRecord untypedRecord)
    {
        return new DataRecord<T>(untypedRecord);
    }
}

现在,示例如下所示:

IDataRecord<Product> productRecord = record.TypedAs<Product>();

string name = productRecord.ReadValue(p => p.Name);
decimal price = productRecord.ReadValue(p => p.Price);
...

【讨论】:

  • 这是一个有趣的方法......我会尽力给你我的反馈!
  • 我试图实现这个解决方案,但对我的应用程序来说太过分了(我需要从同一个 IDataRecord 中取出不同的实体,并且修改太多)。但这是一个正确的答案,所以我会标记它!非常感谢!
  • @Alessandro:该解决方案对您不起作用,这太糟糕了。不过,我很高兴您对此有所了解。从技术上讲,您可以将相同的IDataRecord 实例键入为不同的实体;我只是假设每条记录只有一个实体,但这不是硬性规则。此外,我将其编写为一次性通用实用程序,希望您和其他人能够在许多不同的上下文中使用它。希望您将来能找到一些用处。
  • 是的,Brian,这非常鼓舞人心!感谢您的帮助!
【解决方案2】:

也许这会起作用(我没有测试过):

public static void ReadValue<T1, T2>(this IDataRecord record, T1 product, Expression<Func<T1, T2>> expression)
{
    MemberExpression body = (MemberExpression)expression.Body;
    string fieldName = body.Member.Name;
    int ordinal = record.GetOrdinal(fieldName);
    var val = (T2)(record.IsDBNull(ordinal) ? default(T2) : record.GetValue(ordinal));
    ((PropertyInfo)body.Member).SetValue(product, val, null);
} 

那么你可以这样称呼它:

record.ReadValue(product, p => p.Name); 

名称只指定一次,编译器推断泛型类型。显然 T1 需要是一个引用类型。

【讨论】:

  • 这很好用,但我并不总是有真正的产品可用。例如我可以写“return record.ReadValue(p => p.ProductID);”没有 Product 类的实例。否则这将是一个绝妙的解决方案!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-07-16
  • 2014-04-30
  • 1970-01-01
  • 2023-01-01
  • 1970-01-01
相关资源
最近更新 更多