dotnet writes an object that supports layer by layer inheritance properties

Keywords: C#

Recently, I am building a table control much worse than Excel. One of the requirements is attribute inheritance. As we all know, there are cells in the table. Text is allowed in the cells, and multiple paragraphs of text can be placed in the text. The protagonist of this paper is the style attributes of text paragraphs, including text font, font size, color and so on. If the attribute of a text paragraph is not specially set, the text style attribute in the cell will be used. If no special text style is specified in the cell, the text style of the current row will be inherited. If the current line does not specifically specify a text style attribute, the default style of the document will be used. The default style of the document will adopt the theme style according to whether there is any special specification

With such a complex layer by layer inheritance logic, if each attribute needs to be found layer by layer, the amount of code will be particularly large. I want to eat the table when I maintain it

To save the table, let's write an object that supports layer by layer inheritance properties. If an attribute cannot be found in the current layer, it will be automatically found in the upper layer. If none is found, the default value is returned

The following is the definition code of this class

    public class FlattenObject
    {
        /// <summary>
        ///Create an object with inheritance
        /// </summary>
        /// <param name="reserved"></param>
        public FlattenObject(FlattenObject? reserved = null)
        {
            Reserved = reserved;
        }

        private FlattenObject? Reserved { get; }
        private Dictionary<string, object> ValueDictionary { get; } = new Dictionary<string, object>();

        /// <summary>
        ///Set attribute value
        /// </summary>
        /// <param name="value"></param>
        /// <param name="propertyName"></param>
        protected void SetValue(object value, [CallerMemberName] string propertyName = null!)
        {
            ValueDictionary[propertyName] = value;
        }

        /// <summary>
        ///Get property value
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="propertyName"></param>
        /// <returns></returns>
        protected T? GetValue<T>([CallerMemberName] string propertyName = null!)
            => GetValue<T>(default!, propertyName);

        /// <summary>
        ///Get property value
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="defaultValue"></param>
        /// <param name="propertyName"></param>
        /// <returns></returns>
        protected T GetValue<T>(T defaultValue, [CallerMemberName] string propertyName = null!)
        {
            if (ValueDictionary.TryGetValue(propertyName, out var value))
            {
                return (T) value;
            }
            else
            {
                if (Reserved is not null)
                {
                    return Reserved.GetValue<T>(defaultValue, propertyName);
                }
                else
                {
                    return defaultValue;
                }
            }
        }
    }

The Reserved attribute represents the object of the previous layer of the current layer, which is used to inherit the current layer. Because each layer contains the objects of the previous layer, you can automatically find the value of the attribute layer by layer from the lowest layer

By inheriting the current type, you can write the following code

        class FooFlattenObject : FlattenObject
        {
            public FooFlattenObject(FlattenObject reserved = null) : base(reserved)
            {
            }

            public string FontName
            {
                set => SetValue(value);
                get => GetValue<string>();
            }

            public int Count
            {
                set => SetValue(value);
                get => GetValue<int>();
            }
        }

As in the above code, the set and get of each attribute are replaced by calling methods without defining fields

Let's try to write a unit test

            "Given an inheritable object, you can get the property value from the inherited object".Test(() =>
            {
                var reserved = new FooFlattenObject()
                {
                    FontName = "Microsoft YaHei "
                };

                var fakeFlattenObject = new FooFlattenObject(reserved);
                Assert.AreEqual("Microsoft YaHei ", fakeFlattenObject.FontName);

                fakeFlattenObject.Count = 2;
                Assert.AreEqual(2, fakeFlattenObject.Count);
                Assert.AreEqual(0, reserved.Count);
            });

You can see that the value of FontName is set in the reserved object, which can be inherited by fakeFlattenObject. At the same time, the automatically read code is almost empty for the upper level business

Setting the Count value of fakeFlattenObject will not affect the reserved object

Through this method, the code with layers of inheritance logic does not need a lot of repetition. In addition to being used in tables, it can also be used in the logic that needs to inherit attributes, such as parsing the text in the shape of PPT, such as picture clipping of PPT

The above code also has shortcomings, that is, it is not friendly to structures, such as bool or int, which need to be converted to object storage

Posted by mars_rahul on Tue, 09 Nov 2021 21:57:35 -0800