2

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
}
3
  • Perhaps overriding Condition<T>.ToString() to return Value.ToString() would be enough in your case? Commented May 9, 2018 at 17:43
  • Also, I think c.GetType() == typeof(string) will never match, perhaps you need to expose additional field in ConditionBase interface that would expose the wrapped type? Commented May 9, 2018 at 17:45
  • @orhtej2 I wasn't sure if c.GetType() == typeof(string) would work or not. I hadn't run the code yet because of the build error. I like the ToString() idea, but I would need to extend it a bit because I could have fields of type decimal, Boolean, etc. Commented May 9, 2018 at 17:49

1 Answer 1

3

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 ";
}
Sign up to request clarification or add additional context in comments.

4 Comments

This would work if I wanted everything to be a string. Unfortunately, sometimes I need a string and sometimes I need a different type (e.g. int or DateTime). I updated my code with more details and I fixed a few of the syntax things that I just typed incorrectly in the example (my code had the correct syntax)
@TimHutchison - See solution #2 (everything added). You could also add logic to transform the value to a string but you would need to provide a converter either per type or included it in the constructor. Alternatively you could override ToString which would be the best solution if this is strictly for display purposes like during debugging or presentation on screen.
@TimHutchison I think the idea presented here with Linq's OfType<T> should work quite nicely for all the types. If it doesn't satisfy your needs then perhaps you can update the original question with desired use case (some kind of pseudocode?)
@orhtej2 On second look, the OfType<T> would work well with multiple loops. I like the ToString() a little better so I am going to try some things down that path first

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.