locked
Creating a Class with a variable number of fields RRS feed

  • Question

  • I am working in a project that has a certain amount of possible data inputs. These data inputs have incremental amount of information.

    An example, for clarity: Inputs can be blocks of data called A, B and C. Block A has got 2 fields - F1 and F2. Block B has got 3 fields - F1, F2 and F3. Block C has got 4 fields - F1, F2, F3 and F4. Fields with equal names represent the same information, so you can say that type C contains type B and so on.

    My problem seems like an inheritance problem at first. So, I'm currently using a class to each type of data block, A, B and C, and then inheriting the previous object (C : B, B : A). But the thing is, if I have a lot of these objects I end up with a lot of classes. When i'm creating these data blocks at runtime, I'm always using the constructor for the specific class for that data type. The thing is, each constructor has a different amount of arguments, since the number of fields varies. So i was wondering if I could use some kind of overload in a single class. But if I use a single class with all 4 fields, I end up with unneeded data in almost all data blocks, and my class methods would have to also differentiate between data types somehow.

    I was wondering if there is a more polished way to solve my problem. Is there a way to create a single class that will only contain the information needed? I searched around for a bit and found out about dynamic objects, but was unable to wrap my head around how it could (or should) be used. I'm not a very experienced programmer, so I'm pretty sure I'm missing something trivial here...

    Thank you in advance.


    THML-MSDN FORUM

    Tuesday, June 23, 2020 12:27 AM

All replies

  • Greetings thml300.

    If the fields are all of the same type, or implement the same interface, perhaps you could put them into a List. The length of the List could vary according to the number of fields.

    • Marked as answer by thml300 Tuesday, June 23, 2020 6:50 PM
    • Unmarked as answer by thml300 Tuesday, June 23, 2020 6:59 PM
    Tuesday, June 23, 2020 1:09 AM
  • Hi thml300,

    Thank you for posting here.

    If you need a class with a dynamic number of attributes, you can use ExpandoObject.

                dynamic contact = new ExpandoObject();
                contact.Name = "Patrick Hines";
                contact.Phone = "206-555-0144";
                contact.Address = new ExpandoObject();
                contact.Address.Street = "123 Main St";
                contact.Address.City = "Mercer Island";
                contact.Address.State = "WA";
                contact.Address.Postal = "68402";

    Or, you can write a class that inherits DynamicObject.

            public sealed class MyDynObject : DynamicObject
            {
                private readonly Dictionary<string, object> _properties;
    
                public static dynamic GetDynamicObject(Dictionary<string, object> properties)
                {
                    return new MyDynObject(properties);
                }
                public MyDynObject(Dictionary<string, object> properties)
                {
                    _properties = properties;
                }
    
                public override IEnumerable<string> GetDynamicMemberNames()
                {
                    return _properties.Keys;
                }
    
                public override bool TryGetMember(GetMemberBinder binder, out object result)
                {
                    if (_properties.ContainsKey(binder.Name))
                    {
                        result = _properties[binder.Name];
                        return true;
                    }
                    else
                    {
                        result = null;
                        return false;
                    }
                }
    
                public override bool TrySetMember(SetMemberBinder binder, object value)
                {
                    if (_properties.ContainsKey(binder.Name))
                    {
                        _properties[binder.Name] = value;
                        return true;
                    }
                    else
                    {
                        return false;
                    }
                }
            }

                var dyn = MyDynObject.GetDynamicObject(new Dictionary<string, object>()
                            {
                                {"ID", 1},{ "Name","Timon"}
                            });
    
                Console.WriteLine(dyn.ID);
                Console.WriteLine(dyn.Name);
    
                dyn.ID = 150;
                dyn.Name = "Timon1";
    
                Console.WriteLine(dyn.ID);
                Console.WriteLine(dyn.Name);

    Dynamically Add C# Properties at Runtime

    Best Regards,

    Timon


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Tuesday, June 23, 2020 5:54 AM
  • I think ExpandoObject is the best choice here but it is for an extreme case. It is harder to work with data in this format because you have almost no compile time safety. At that point you are basically working with a dictionary of objects.

    In my experience the problem can be solved by using inheritance in combination with a factory class for creation but this is an exception rather than the norm. I think you need to reconsider your initial solution. You immediately went to inheritance because you saw data point A having a subset of data that data point B has and naturally you learned OOP solves this. But inheritance indicates an "is-a" relationship and that doesn't seem to apply here. Just because A and B have fields with the same name does not make them related at all. After all a state from a database might have an Id and Name property but so might a product. This doesn't make them related and therefore inheritance doesn't apply.

    Ultimately it depends on what your data means. Is "block B" a "block A" with more data? Is "block C" a "block B" with more data? If so then inheritance is appropriate. If not then they are just different data points with the similar fields.

    You mentioned you are going to have a lot of these. Does that mean only the fields differ? If so then perhaps you need to stop thinking in terms of class/properties and more about collections of data. Suppose you can have arbitrary fields in a block. How might you represent that? Perhaps a dictionary that maps names to the underlying data.

    public class Field
    {
       public Field ( string name )
       {
          Name = name;
       }
    
       public string Name { get; }
       public object Value { get; set; }
    }
    
    //Helper type
    public class Field<T> : Field
    {
       public Field ( string name ) : base(name)
       {
       }
    
       public T TypedValue
       {
          get => (T)Value;
          set => Value = value;
       }
    }
    
    public class Block
    {
       public IEnumerable<Field> Fields => _fields.Values;
    
       public T GetField<T> ( string name )
           => _fields.TryGetValue(name, out var field) ? (T)field.Value : default(T);
    
       public void SetField<T> ( string name, T value )
           => _fields[name] = new Field(name) { Value = value };
    
       private readonly Dictionary<string, Field> _fields = new Dictionary<string, FIeld>();
    }

    Of course you lose some compile time type safety but you're going to run into that anyway if you use a dynamic object. 

    var block = new Block();
    block.SetField("F1", 10);
    block.SetField("F2", 20.5);
    
    var value = block.GetField<int>("F1");

    But now you can create derived types for the common blocks you might programmatically use and nobody would care.

    public class BlockA : Block
    {
        public int F1 
        {
            get => GetField<int>("F1");
            set => SetField("F1", value);
        }
    
        public double F2
        {
            get => GetField<double>("F2");
            set => SetField("F2", value);
        }
    }
    
    //later
    var blocks = new List<Block>();
    
    var a = new BlockA();
    a.F1 = 10;
    a.F2 = 45.6;
    blocks.Add(a);
    
    //And even later
    var aBlocks = blocks.OfType<BlockA>();
    foreach (var aBlock in aBlocks)
    {
       Console.WriteLine($"F1 = {aBlock.F1}, F2 = {aBlock.F2}");
    };
    
    


    Michael Taylor http://www.michaeltaylorp3.net

    • Marked as answer by thml300 Tuesday, June 23, 2020 6:50 PM
    • Unmarked as answer by thml300 Tuesday, June 23, 2020 7:00 PM
    Tuesday, June 23, 2020 1:59 PM