以与PHP array_merge类似的方式合并两个对象

In PHP it's common practice to pass an array as options for a class, and then merge that array with a set another array that holds the defaults.

Something like this.

class MyObject
{
     private static $defaults = array('value'=>10);
     private $settings;

     public function Something(array $settings = array())
     {
          $this->settings = array_merge(static::defaults,$settings);
     }
}

You can do the something in JavaScript using jQuery or other libraries that introduce the merge function. These scripts let you take two Javascript objects and merge them together. Allowing you to use one as the defaults, and another to override those defaults.

I've found this pattern very useful, because it allows you to configure a large set of defaults but only assign the settings you need.

Is there anyway to do something like this in C#?

I could write a function that uses reflection to do this on public properties, but I was thinking something like this must have already been done.

EDIT:

This question has been asked before on stack, but not answered in a way that provides the same simplicity as what can be done in PHP and Javascript.

dqlxtv1452
dqlxtv1452 你有更多的C#安全方法来处理可能有默认值的选项吗?
7 年多之前 回复
dslfjrmz70457
dslfjrmz70457 我宁愿说这是一个错综复杂,不安全且容易出错的问题,而不是一种简单的方法。C#提供了一种通过ObjectInitializer语法构造对象的简便方法
7 年多之前 回复
duanbi3151
duanbi3151 我希望它是一种在新对象上设置一组属性的简单方法。
7 年多之前 回复
doudu3961
doudu3961 人们不会在C#中这样做,因为C#是一种静态类型的语言。字符串不用于表示对象的属性。你想要什么?
7 年多之前 回复
donglin5770
donglin5770 可能重复:stackoverflow.com/questions/1358877/...
7 年多之前 回复

1个回答

I wasn't able to find an answer that did exactly what I wanted. So I wrote a small method to do this. It'll take two objects of the same time, and merge their fields/properties assuming that a null value represents an unassigned field/property.

Here is the usage example. Create a class to hold options for a communications class, let the communication class have defaults and then initialize the communication with user settings.

An example settings class.

public class ComSettings
{
    public int? Port;
    public string? Address;
    public bool? KeepAlive;
}

An example class that uses those settings in the constructor.

public class ComLibrary
{
    private static ComSettings _defaults = new ComSettings { Port = 80, Address = "localhost" };

    protected ComSettings settings;

    public ComLibrary(ComSettings pSettings)
    {
        this.settings = ObjectMerge<ComSettings>(_defaults, pSettings);
    }
}

This will let different classes use the ComSettings but each could have different defaults. The only limitation is that the field/properties have to support null assignments.

Here's the implementation of ObjectMerge.

    /// <summary>
    /// Creates a new object that contains the properties of the two objects merged together.
    /// </summary>
    /// <typeparam name="T">The class type to merge.</typeparam>
    /// <param name="pDefaults">Instance of the defaults object.</param>
    /// <param name="pSettings">Instance of the settings object.</param>
    /// <returns>A new instance of T with the merged results.</returns>
    public static T ObjectMerge<T>(T pDefaults, T pSettings, bool pMergeFields = true, bool pMergeProperties = true) where T : class, new()
    {
        T target = new T();
        Type type = typeof(T);
        List<MemberInfo> infos = new List<MemberInfo>(type.GetMembers());

        foreach (MemberInfo info in infos)
        {
            // Copy values from either defaults or settings
            if (pMergeFields && info.MemberType == MemberTypes.Field)
            {
                FieldInfo field = (FieldInfo)info;
                if (field.IsPublic)
                {
                    object value = field.GetValue(pSettings);
                    value = (value == null) ? field.GetValue(pDefaults) : value;
                    field.SetValue(target, value);
                }
            }

            // Copy values from either defaults or settings
            if (pMergeProperties && info.MemberType == MemberTypes.Property)
            {
                PropertyInfo prop = (PropertyInfo)info;
                if (prop.CanWrite && prop.CanRead)
                {
                    object value = prop.GetValue(pSettings, null);
                    value = (value == null) ? prop.GetValue(pDefaults, null) : value;
                    prop.SetValue(target, value, null);
                }
            }
        }

        return target;
    }

And here is a simple unit test.

/// <summary>
///This is a test class for CoreUtilsTest and is intended
///to contain all CoreUtilsTest Unit Tests
///</summary>
[TestClass()]
public class CoreUtilsTest
{
    /// <summary>
    /// A class to perform testing on.
    /// </summary>
    public class MyClassA
    {
        public string Param1;
        public string Param2;
        public string Param3;
    }

    /// <summary>
    /// A class to perform testing on.
    /// </summary>
    public class MyClassB
    {
        private string _param1;

        public string Param1
        {
            get { return _param1; }
            set { _param1 = value; }
        }
        private string _param2;

        public string Param2
        {
            get { return _param2; }
            set { _param2 = value; }
        }
        private string _param3;

        public string Param3
        {
            get { return _param3; }
            set { _param3 = value; }
        }
    }

    /// <summary>
    ///A test for SetProperties
    ///</summary>
    [TestMethod()]
    public void Merging_Fields()
    {
        MyClassA defaults = new MyClassA { Param1 = "defaults" };
        MyClassA settings = new MyClassA { Param2 = "settings" };
        MyClassA results = CoreUtils.ObjectMerge<MyClassA>(defaults, settings);

        Assert.AreEqual("defaults", results.Param1);
        Assert.AreEqual("settings", results.Param2);
        Assert.AreEqual(null, results.Param3);
    }

    [TestMethod()]
    public void Merging_Properties()
    {
        MyClassB defaults = new MyClassB { Param1 = "defaults" };
        MyClassB settings = new MyClassB { Param2 = "settings" };
        MyClassB results = CoreUtils.ObjectMerge<MyClassB>(defaults, settings);

        Assert.AreEqual("defaults", results.Param1);
        Assert.AreEqual("settings", results.Param2);
        Assert.AreEqual(null, results.Param3);
    }

}
立即提问
相关内容推荐