【问题标题】:More elegant way of updating empty object properties更新空对象属性的更优雅的方式
【发布时间】:2014-03-21 20:50:33
【问题描述】:

我目前正在编写一个填充对象缺失属性的方法。该对象已从数据库中设置了一些值,但如果任何值为空,则它会转到另一个数据源(长话短说)。

这意味着我的代码已经变得有点像下面的sn-p了

if(string.IsNullOrEmpty(myObject.FieldA))
       myObject.FieldA = UpdateFromMethod("FieldA");
if(string.IsNullOrEmpty(myObject.FieldB))
       myObject.FieldB = UpdateFromMethod("FieldB");
if(string.IsNullOrEmpty(myObject.FieldC))
       myObject.FieldC = UpdateFromMethod("FieldC");

这是我必须忍受的事情,还是有更好的方法来做到这一点?

【问题讨论】:

  • 这三种情况都应该使用same方法(UpdateFromMethod)吗?
  • 您在没有将参数传递给UpdateFromMethod 的情况下填充数据?在这种情况下,所有字段的值都相同
  • 它在所有三种情况下都使用相同的方法 - 但它只在现有属性为空或 null 时调用它 - 真正的问题是多个 null 检查。
  • 您的编辑显示字段名称的用法,元编程方法实际上可能非常可行......但这真的取决于您有多疯狂。

标签: c# oop


【解决方案1】:

对于特定类型的场景,丑陋重复代码的唯一真正替代方案是丑陋的元编程代码 - 至少当前代码是可读的。如果您测试的只是 null,则 null-coalescing (??) 可能会使其更整洁,但从根本上说,您的代码可以工作。

如果它们真的是字段(而不是属性),您可以可能会执行以下操作:

void Update(ref string value, Func<string> source)
{
    if(string.IsNullOrEmpty(value)) value = source();
}
...
Update(ref myObject.FieldA, UpdateFromMethodA);
Update(ref myObject.FieldB, UpdateFromMethodB);
Update(ref myObject.FieldC, UpdateFromMethodC);

但在幕后创建了大量的委托实例,使其不受欢迎。

坦率地说,我会坚持你所拥有的。

【讨论】:

  • 这样做的主要优点是它减少了在一个地方更改字段名称而不是另一个地方的复制粘贴错误。通过只指定一次字段名称,只有一个地方可以更改它。
【解决方案2】:

使用反射。

var type = myObject.GetType();

foreach (var field in type.GetFields())
{
    string value = (string)field.GetValue(myObject);

    if (string.IsNullOrEmpty(value))
    {
        UpdateFromMethod(field.Name);
    }
}

【讨论】:

  • 以可读性换取性能;可接受程度取决于具体情况(是否在热路径中?) - 在许多情况下,这可能非常好(所以肯定是 +1)。在许多其他情况下,这需要ILGenerator 关注。
  • 我非常喜欢这个想法,因此投了赞成票,但如果我开始使用反射,我确实担心我的应用程序的性能 - 许多这些对象被创建并可能更新,所以这可能不会在这种情况下是最好的方法。再加上一些字段不需要被调用(你当时不知道)。好主意。
【解决方案3】:

您可以将此逻辑放在属性中

private string _myProp;
public MyProp
{
    get { return _myProp ?? GetValueFromMethod(); }
    set { _myProp = value; }
}

如果左边的值为空,?? 运算符是产生右边值的合并运算符。

或者如果您还需要测试空字符串:

public MyProp
{
    get { return IsNullOrEmpty(_myProp) ? GetValueFromMethod() : _myProp; }
    set { _myProp = value; }
}

如果必须在调用 setter 之前设置它,您也可以将逻辑放在 setter 中并初始化支持变量。比前两个例子的优势:多次调用getter时,方法只调用一次。

 private string _myProp = GetValueFromMethod();
 public MyProp
 {
    get { return _myProp; }
    set { _myProp = IsNullOrEmpty(value) ? GetValueFromMethod() : value; }
 }

将逻辑放在 setter 和 getter 中,作为另一种替代方案,与在字段初始化程序中调用它相比,它的优点是以惰性方式调用该方法,并且它像以前一样只被调用一次。

【讨论】:

  • 这不是同一个逻辑测试(null 与 null-or-empty),并且它在存储值方面的行为不同 - 当 _myProp 为 null 时,它将执行GetValueFromMethod() 每次)。它还在一个非常不同的时间运行(成员访问与故意的单个时间点)
  • 这是在每次获取时重新计算值,当设置值为空时。
  • 是的,这不是一个有效的重构,但它是一个可行的替代方案。
【解决方案4】:

您还可以使用 lambda 表达式的动态评估和反射来设置类的特定属性。从性能的角度来看,这可能不是最佳解决方案,但该方法适用于各种数据类型:

using System;
using System.Linq.Expressions;
using System.Reflection;

public class DataClass
{
    public string Prop1 { get; set; }
    public string Prop2 { get; set; }
    public string Prop3 { get; set; }
    public int Prop4 { get; set; }
    public int? Prop5 { get; set; }
}

public class Test
{
    public static void Main()
    {
        var cls = new DataClass() { Prop1 = null, Prop2 = string.Empty, Prop3 = "Value" };
        UpdateIfNotSet(cls, x => x.Prop1, UpdateMethod);
        UpdateIfNotSet(cls, x => x.Prop2, UpdateMethod);
        UpdateIfNotSet(cls, x => x.Prop3, UpdateMethod);
        UpdateIfNotSet(cls, x => x.Prop4, (x) => -1);
        UpdateIfNotSet(cls, x => x.Prop5, (x) => -1);
        Console.WriteLine(cls.Prop1);  // prints "New Value for Prop1"
        Console.WriteLine(cls.Prop2);  // prints "New Value for Prop2"
        Console.WriteLine(cls.Prop3);  // prints "Value"
        Console.WriteLine(cls.Prop4);  // prints "0"
        Console.WriteLine(cls.Prop5);  // prints "-1"
    }

    public static void UpdateIfNotSet<TOBJ, TPROP>(TOBJ obj, 
                           Expression<Func<TOBJ, TPROP>> prop, 
                           Func<string, TPROP> updateMth)
    {
        var currentValue = prop.Compile().DynamicInvoke(obj);
        var strValue = currentValue as string; // Need to convert to string gracefully so that check against empty is possible
        if (currentValue == null || (strValue != null && strValue.Length == 0))
        {
            var memberAcc = (MemberExpression)prop.Body;
            var propInfo = (PropertyInfo)memberAcc.Member;
            propInfo.SetMethod.Invoke(obj, new object[] { updateMth(propInfo.Name) });
        }
    }

    public static string UpdateMethod(string propName)
    {
        return "New Value for " + propName;
    }
}

UpdateIfNotSet 方法需要以下参数:

  • 一个对象
  • 访问对象属性的 lambda 表达式
  • 检索更新值的函数。请注意,该函数在调用时接受一个字符串作为参数,该参数填充属性名称

UpdateIfNotSet 方法的通用参数由编译器推断,因此您不必指定它们。

该方法首先编译 lambda 表达式并通过调用已编译的表达式来检索属性的当前值。然后它检查该值是 null 还是空字符串。在这种情况下,它通过调用提供的方法为属性分配一个新值。

【讨论】:

    猜你喜欢
    • 2011-08-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-12
    相关资源
    最近更新 更多