简体   繁体   中英

Design issue in C#: Generics or polymorphism or both?

I need some help with a design issue I'm having. What I try to achieve is this:

I have a main class called Document. This Class has a list of Attribute classes. These Attribute classes have some common properties such as Identity and Name, but they differ in one property I call Value. The Value type is different for each different Attribute class and be of type string, integer, List, DateTime, float, List and also classes that consists of several properties. One example would be a class I would call PairAttribute that have 2 properties: Title and Description.

What I try to achieve is type safety for Value property of the Attribute child classes and that these child classes should be able to be added to the Attribute list in the Document class. I could have made only one Attribute class that have a Value property of type object and be done with it, but that is exactly what I try to avoid here.

The common properties (Identity and Name) should be placed in a base class I guess, lets call that AttributeBase class. But I want to have a child class, say StringAttribute, where the Value property is of type string, a IntegerAttribute class where the Value property is of type Integer, a StringListAttribute where the Value property is of type List, a PairAttribute class where the Value is a class with several properties, etc

Do you know how I can implement this? Is this a solution I should go for at all or is it a better ways of solving this type safety issue? I would appreciate code examples for clarification:)

You don't specify a language, but the feature described by the term "generics" (as used by Java and C#) is often called " parametric polymorphism " in other languages (like ML and Haskell). Conversely, the common meaning of " polymorphism " in Java and C# is actually more precisely called " subtype polymorphism ".

The point is, whether you are using subtype polymorphism or parametric polymorphism, either way your problem calls for polymorphism, so I think you're on the right track.

The question really boils down to: when is parametric polymorphism better than subtype polymorphism? The answer is actually quite simple: when it requires you to write less code.

So, I'd suggest you prototype both approaches, and then see which one leads to simpler, easier to understand code. Then do it that way.

You may pass with a generic Attribute instead of inheritance/method polymorphism, but you will need to store them in some list and for that you will need an interface, because datastores in.Net cannot be a collections of undefined generic types, and if you would define them you would not be able to mix types inside:

    public interface IAttribute {
        string Identity { get; set; }
        string Name { get; set; }
        T GetValue<T>();
    }

    public class Attribute<T> : IAttribute
    {

        public string Identity { get; set; }
        public string Name { get; set; }
        public T Value { get; set; }
        public Tret GetValue<Tret>() {
            return (Tret)(Object)Value;
        }
    }

    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            List<IAttribute> lst = new List<IAttribute>();
            Attribute<string> attr1 = new Attribute<string>();
            attr1.Value = "test";

            Attribute<int> attr2 = new Attribute<int>();
            attr2.Value = 2;

            lst.Add(attr1);
            lst.Add(attr2);

            string attr1val = lst[0].GetValue<string>();
            int attr2val = lst[1].GetValue<int>();

        }
    }

The (Tret)(Object) actually does not change type, it only boxes T and the (Tret) unboxes the value without using middle variables. This will of course fail if you miss the right type when calling GetValue<type>() . Even if compatible types are sent like Value is integer and you do GetValue<double>() -> because unboxing an integer into a double is not allowed.

Boxing/Unboxing is not as fast as casting but it ensures type safety is preserved, and there is at my knowledge no way to use generics with an compile time known interface in some other way.

So this should be type safe... and without a lot of code.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM