drzdu44226 2013-05-19 17:28
浏览 72
已采纳

以与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.

  • 写回答

1条回答 默认 最新

  • dongqiao1151 2013-05-19 21:17
    关注

    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);
        }
    
    }
    
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥50 导入文件到网吧的电脑并且在重启之后不会被恢复
  • ¥15 (希望可以解决问题)ma和mb文件无法正常打开,打开后是空白,但是有正常内存占用,但可以在打开Maya应用程序后打开场景ma和mb格式。
  • ¥20 ML307A在使用AT命令连接EMQX平台的MQTT时被拒绝
  • ¥20 腾讯企业邮箱邮件可以恢复么
  • ¥15 有人知道怎么将自己的迁移策略布到edgecloudsim上使用吗?
  • ¥15 错误 LNK2001 无法解析的外部符号
  • ¥50 安装pyaudiokits失败
  • ¥15 计组这些题应该咋做呀
  • ¥60 更换迈创SOL6M4AE卡的时候,驱动要重新装才能使用,怎么解决?
  • ¥15 让node服务器有自动加载文件的功能