Search code examples
c#genericsgeneric-list

Reading Generic Value from List of Generic Objects


I am trying to loop through a list of generic objects call Condition<T> to read the generic field Value. I followed this question to be able to store the List<Condition<T>>. The issue I am running into now is that I can't use the Value field in my loop. What do I need to change in order to use the Value field?

Main

string url = "";
List<ConditionBase> Conditions = new List<ConditionBase>();
Conditions.Add(new Condition<int>(Field.Field1, 1, ConditionOperator.Equal))
Conditions.Add(new Condition<string>(Field.Field2, "test", ConditionOperator.NotEqual))

foreach (ConditionBase c in Conditions)
{
    if (c.GetType() == typeof(string))
    {
        // c.Value throws an error
        url += c.Field + " " + c.ConditionOperator + " '" + c.Value + "' and ";
    }
    else if (c.GetType() == typeof(DateTime))
    {
        // c.Value throws an error
        url += c.Field + " " + c.ConditionOperator + " " + Helpers.FormatDate(c.Value) + " and ";
    }
}

Condition Base

public interface ConditionBase
{
    Field Field { get; set; }
    ConditionOperator ConditionOperator { get; set; }
}

Condition

public class Condition<T> : ConditionBase
{
    private Field _Field;
    private T _Value;
    private ConditionOperator _ConditionOperator;

    public Condition(Field field, T value, ConditionOperator condition)
    {
        this._Field = field;
        this._Value = value;
        this._ConditionOperator = condition;
    }

    public Field Field
    {
        get
        {
            return this._Field;
        }
        set
        {
            if (this._Field != value)
            {
                this._Field = value;
            }
        }
    }

    public T Value
    {
        get
        {
            return this._Value;
        }
        set
        {
            if (!EqualityComparer<T>.Default.Equals(this._Value, value))
            {
                this._Value = value;
            }
        }
    }

    public ConditionOperator ConditionOperator
    {
        get
        {
            return this._ConditionOperator;
        }
        set
        {
            if (this._ConditionOperator != value)
            {
                this._ConditionOperator = value;
            }
        }
    }
}

Enums

public enum Field{
    Field1,
    Field2
}

public enum ConditionOperator{
    Equal,
    NotEqual,
    GreaterThan,
    LessThan
}

Solution

This solution is based on the comments by @orhtej2 & the answer by @Igor.

Main - Test

static void Main(string[] args)
{
    var x1 = new Condition<int>(new Field(), 123, ConditionOperator.Equal);
    var x2 = new Condition<string>(new Field(), "test", ConditionOperator.Equal);
    var x3 = new Condition<DateTime>(new Field(), new DateTime(2018,5,5), ConditionOperator.Equal);

    var qqq = new List<ConditionBase>();

    qqq.Add(x1);
    qqq.Add(x2);
    qqq.Add(x3);

    foreach (ConditionBase c in qqq)
    {
        Console.WriteLine(c.GetValue());
    }
    Console.ReadLine();
}

Condition Base

public interface ConditionBase
{
    Field Field { get; set; }
    ConditionOperator ConditionOperator { get; set; }
    string GetValue();
}

Condition

public class Condition<T> : ConditionBase
{
    private Field _Field;
    private T _Value;
    private ConditionOperator _ConditionOperator;

    public Condition(Field field, T value, ConditionOperator condition)
    {
        this._Field = field;
        this._Value = value;
        this._ConditionOperator = condition;
    }

    public Field Field
    {
        get
        {
            return this._Field;
        }
        set
        {
            if (this._Field != value)
            {
                this._Field = value;
            }
        }
    }

    public T Value
    {
        get
        {
            return this._Value;
        }
        set
        {
            if (!EqualityComparer<T>.Default.Equals(this._Value, value))
            {
                this._Value = value;
            }
        }
    }

    public ConditionOperator ConditionOperator
    {
        get
        {
            return this._ConditionOperator;
        }
        set
        {
            if (this._ConditionOperator != value)
            {
                this._ConditionOperator = value;
            }
        }
    }

    public string GetValue()
    {
        if (Value is string)
        {
            return "'" + Value.ToString() + "'";
        }
        else if (Value is DateTime)
        {
            return Helpers.FormatDate(Convert.ToDateTime(Value));
        }
        else
        {
            return Value.ToString();
        }            
    }
}

Enums

public enum Field{
    Field1,
    Field2
}

public enum ConditionOperator{
    Equal,
    NotEqual,
    GreaterThan,
    LessThan
}

Solution

  • You have syntax errors in the code like the lacking public scope of your enums and ConditionOperator.Equal (not ConditionOperator.Equals) but that asside here is the fix.

    1. Conditions should be of type List<ConditionBase>
    2. Use OfType on the List to retrieve and cast the resulting type to Condition<string>. I assume that this was your intention with your added check c.GetType() == typeof(string)
    string url = "";
    List<ConditionBase> Conditions = new List<ConditionBase>();
    Conditions.Add(new Condition<int>(Field.Field1, 1, ConditionOperator.Equal));
    Conditions.Add(new Condition<string>(Field.Field2, "test", ConditionOperator.NotEqual));
    
    foreach (var c in Conditions.OfType<Condition<string>>())
    {
        url += c.Field + " " + c.ConditionOperator + " '" + c.Value + "' and ";
    }
    

    If you want a generic property that you can access on all instances regardless of the generic type constraint then you would need to extend the base interface accordingly.

    public interface ConditionBase
    {
        Field Field { get; set; }
        ConditionOperator ConditionOperator { get; set; }
        object FieldValue { get; }
    }
    
    public class Condition<T> : ConditionBase
    {
      /* I only included the added code in this type */
      public object FieldValue
      {
          get { return (object) this.Value; }
      }
    }
    
    string url = "";
    List<ConditionBase> Conditions = new List<ConditionBase>();
    Conditions.Add(new Condition<int>(Field.Field1, 1, ConditionOperator.Equal));
    Conditions.Add(new Condition<string>(Field.Field2, "test", ConditionOperator.NotEqual));
    
    foreach (var c in Conditions)
    {
        url += c.Field + " " + c.ConditionOperator + " '" + c.FieldValue + "' and ";
    }
    

    It seems you want to output your value to a string based on the changes in your question. Add a string formatter to your type.

    /* I only included the added code in this type */
    public class Condition<T> : ConditionBase
    {
      private Func<T, string> _formatValue;
      public Condition(Field field, T value, ConditionOperator condition, Func<T, string> formatValue)
      {
        this._Field = field;
        this._Value = value;
        this._ConditionOperator = condition;
        this._formatValue = formatValue;
      }
    
      public override string ToString()
      {
          return this._formatValue(this.Value);
      }
    }
    
    string url = "";
    List<ConditionBase> Conditions = new List<ConditionBase>();
    Conditions.Add(new Condition<int>(Field.Field1, 1, ConditionOperator.Equal, (val)=> val.ToString()));
    Conditions.Add(new Condition<string>(Field.Field2, "test", ConditionOperator.NotEqual, (val)=> val));
    
    foreach (var c in Conditions)
    {
        url += c.Field + " " + c.ConditionOperator + " '" + c.ToString() + "' and ";
    }