2

あるオブジェクトの多くのプロパティを別のオブジェクトから更新しようとしていますが、この同じコードを何度も繰り返すことになります (Name と LastName の例を示していますが、同様のコードを持つ他の 15 のプロパティがあります)。

ただし、すべてのプロパティではないことに注意することが重要であるため、やみくもにすべてをコピーすることはできません。

 public class Person
 {

     public bool UpdateFrom(Person otherPerson)
     {

        if (!String.IsNullOrEmpty(otherPerson.Name))
        {
            if (Name!= otherPerson.Name)
            {
                change = true;
                Name = otherPerson.Name;
            }
        }

       if (!String.IsNullOrEmpty(otherPerson.LastName))
        {
            if (LastName!= otherPerson.LastName)
            {
                change = true;
                LastName = otherPerson.LastName;
            }
        }

        return change;
     }
  }

このコードを書くためのよりエレガントな方法はありますか?

4

5 に答える 5

2

を使用しExpressionて、アクセスするフィールドを定義できます。更新を処理するコードは次のようになります。

   Person one = new Person {FirstName = "First", LastName = ""};
   Person two = new Person {FirstName = "", LastName = "Last"};
   Person three = new Person ();

   bool changed = false;
   changed = SetIfNotNull(three, one, p => p.FirstName) || changed;
   changed = SetIfNotNull(three, one, p => p.LastName) || changed;
   changed = SetIfNotNull(three, two, p => p.FirstName) || changed;
   changed = SetIfNotNull(three, two, p => p.LastName) || changed;

||可能であれば.NETは評価を短絡するため、式の順序は重要であることに注意してください。または、Ben が以下のコメントで提案しているようにchanged |= ...、より簡単な代替手段として使用してください。

このSetIfNotNullメソッドは、getter を setter に変換するためにちょっとした Expression マジックを行うこの別のメソッドに依存しています。

    /// <summary>
    /// Convert a lambda expression for a getter into a setter
    /// </summary>
    public static Action<T, U> GetSetter<T, U>(Expression<Func<T, U>> expression)
    {
        var memberExpression = (MemberExpression)expression.Body;
        var property = (PropertyInfo)memberExpression.Member;
        var setMethod = property.GetSetMethod();

        var parameterT = Expression.Parameter(typeof(T), "x");
        var parameterU = Expression.Parameter(typeof(U), "y");

        var newExpression =
            Expression.Lambda<Action<T, U>>(
                Expression.Call(parameterT, setMethod, parameterU),
                parameterT,
                parameterU
            );

        return newExpression.Compile();
    }


    public static bool SetIfNotNull<T> (T destination, T source, 
                            Expression<Func<T, string>> getter)
    {
        string value = getter.Compile()(source);
        if (!string.IsNullOrEmpty(value))
        {
            GetSetter(getter)(destination, value);
            return true;
        }
        else
        {
            return false;
        }
    }
于 2013-04-06T18:08:57.387 に答える
0

これは私のコメントを入力しただけです。この手法の詳細については、質問へのコメントを参照してください。

このクラスを定義します。

[AttributeUsage(AttributeTargets.Property)]
public sealed class CloningAttribute : Attribute
{
}

あなたのPersonクラスで:

[Cloning] // <-- applying the attribute only to desired properties
public int Test { get; set; }

public bool Clone(Person other)
{
    bool changed = false;
    var properties = typeof(Person).GetProperties();
    foreach (var prop in properties.Where(x => x.GetCustomAttributes(typeof(CloningAttribute), true).Length != 0))
    {
        // get current values
        var myValue = prop.GetValue(this, null);
        var otherValue = prop.GetValue(other, null);
        if (prop.PropertyType == typeof(string))
        {
            // special treatment for string:
            // ignore if null !!or empty!!
            if (String.IsNullOrEmpty((string)otherValue))
            {
                continue;
            }
        }
        else
        {
            // do you want to copy if the other value is null?
            if (otherValue == null)
            {
                continue;
            }
        }

        // compare and only check 'changed' if they are different
        if (!myValue.Equals(otherValue))
        {
            changed = true;
            prop.SetValue(this, otherValue, null);
        }
    }
    return changed;
}
于 2013-04-06T18:12:13.500 に答える
0

Reflecton を使用してそれを行うことができます。実装例を次に示します (配列などを処理するために追加のコードを追加する必要があります)。

    public class Person
    {
        public bool UpdateFromOther(Person otherPerson)
        {
            var properties =
                this.GetType()
                    .GetProperties(
                        BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty
                        | BindingFlags.GetProperty);


            var changed = properties.Any(prop =>
            {
                var my = prop.GetValue(this);
                var theirs = prop.GetValue(otherPerson);
                return my != null ? !my.Equals(theirs) : theirs != null;
            });

            foreach (var propertyInfo in properties)
            {
                propertyInfo.SetValue(this, propertyInfo.GetValue(otherPerson));
            }

            return changed;
        }

        public string Name { get; set; }
    }

    [Test]
    public void Test()
    {
        var instance1 = new Person() { Name = "Monkey" };
        var instance2 = new Person() { Name = "Magic" };
        var instance3 = new Person() { Name = null};

        Assert.IsFalse(instance1.UpdateFromOther(instance1), "No changes should be detected");
        Assert.IsTrue(instance2.UpdateFromOther(instance1), "Change is detected");
        Assert.AreEqual("Monkey",instance2.Name, "Property updated");
        Assert.IsTrue(instance3.UpdateFromOther(instance1), "Change is detected");
        Assert.AreEqual("Monkey", instance3.Name, "Property updated");

    }
于 2013-04-06T18:07:37.103 に答える
0

Funcとデリゲートを使用すると、Action次のように実行できます。

public class Person
{
    public string Name { get; set; }

    public string LastName { get; set; }

    public bool UpdateFrom(Person otherPerson)
    {
        bool change = false;

        change = Check(otherPerson.Name, p => p.Name, (p, val) => p.Name = val);

        change = change ||
                 Check(otherPerson.LastName, p => p.LastName, (p, val) => p.LastName = val);

        return change;
    }

    public bool Check(string value, Func<Person, string> getMember, Action<Person, string> action)
    {
        bool result = false;

        if (!string.IsNullOrEmpty(value))
        {
            if (getMember(this) != value)
            {
                result = true;
                action(this, value);
            }
        }

        return result;
    }
}
于 2013-04-06T18:05:22.283 に答える
0

You can create generic rewriting tool with will look on properties with particular attribute:

 public class Updater
    {
        public static bool Update(object thisObj, object otherObj)
        {
            IEnumerable<PropertyInfo> props = thisObj.GetType().GetProperties().Where(
                prop => Attribute.IsDefined(prop, typeof(UpdateElementAttribute)));

            bool change = false;
            foreach (var prop in props)
            {
                object value = prop.GetValue(otherObj);

                if (value != null && (value is string || string.IsNullOrWhiteSpace((string)value)))
                {
                    if (!prop.GetValue(thisObj).Equals(value))
                    {
                        change = true;
                        prop.SetValue(thisObj, value);
                    }
                }
            }
            return change;
        }
    }

And then just use it:

public class Person
    {
        public bool UpdateFrom(Person otherPerson)
        {
            return Updater.Update(this, otherPerson);
        }
        [UpdateElement]
        public string Name { get; set; }
        [UpdateElement]
        public string LastName { get; set; }
    }
于 2013-04-06T18:34:25.340 に答える