更新空对象属性的更优雅的方式

本文关键字:方式 更新 对象 属性 | 更新日期: 2023-09-27 17:51:14

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

这意味着我的代码变得有点像下面的代码片段
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");

这是我不得不忍受的,还是有更好的方法?

更新空对象属性的更优雅的方式

对于这种特定的类型的场景,唯一真正替代丑陋的重复代码的是丑陋的元编程代码——至少当前的代码是可读的。如果您要测试的只是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);

,但在幕后,它创建了许多不受欢迎的委托实例。

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

使用反射

var type = myObject.GetType();
foreach (var field in type.GetFields())
{
    string value = (string)field.GetValue(myObject);
    if (string.IsNullOrEmpty(value))
    {
        UpdateFromMethod(field.Name);
    }
}

您可以将此逻辑放在属性

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中,作为另一种替代方法,与在字段初始化器中调用方法相比,它具有以惰性方式调用方法的优点,并且像以前一样只调用一次。

还可以使用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表达式,并通过调用编译后的表达式来检索属性的当前值。然后检查该值是空还是空字符串。在本例中,它通过调用提供的方法为属性分配一个新值。