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 有数据,怎么建立模型求影响全要素生产率的因素
  • ¥50 有数据,怎么用matlab求全要素生产率
  • ¥15 TI的insta-spin例程
  • ¥15 完成下列问题完成下列问题
  • ¥15 C#算法问题, 不知道怎么处理这个数据的转换
  • ¥15 YoloV5 第三方库的版本对照问题
  • ¥15 请完成下列相关问题!
  • ¥15 drone 推送镜像时候 purge: true 推送完毕后没有删除对应的镜像,手动拷贝到服务器执行结果正确在样才能让指令自动执行成功删除对应镜像,如何解决?
  • ¥15 求daily translation(DT)偏差订正方法的代码
  • ¥15 js调用html页面需要隐藏某个按钮