C# classes for system health metrics
A design pattern often used, is to have a non generic base type and a generic type derived from it or a type implementing a non generic as well as a generic interface. An example is

public abstract class Comparer&lt;T&gt; :
    System.Collections.Generic.IComparer&lt;T&gt;, System.Collections.IComparer


You can see the full implementation here public abstract class Comparer&lt;T&gt; : IComparer, IComparer&lt;T&gt;.

You can see that it does an explicit implementaion of IComparer members and an implicit implementation of IComparer&lt;T&gt;. This hides the IComparer members when not accessed through this very interface. Original code:

public abstract int Compare(T x, T y);

int IComparer.Compare(object x, object y) {
    if (x == null) return y == null ? 0 : -1;
    if (y == null) return 1;
    if (x is T &amp;&amp; y is T) return Compare((T)x, (T)y);
    ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArgumentForComparison);
    return 0;
}


A possible approach for your metric class, would be to implement two interfaces IMetric and IMetric&lt;T&gt;. The collection would then have an element type of IMetric.

public interface IMetric
{
    string Name { get; }
    object Value { get; }
    eLogicalOperator LogicalOperator { get; }
    object Threshold { get; }
    bool Faulty();
}

public interface IMetric&lt;out T&gt; : IMetric
{
    new T Value { get; }
    new T Threshold { get; }
}


Implementation:

public class Metric&lt;T&gt; : IMetric&lt;T&gt;
{
    public Metric(string name, eLogicalOperator logicalOperator, T value, T threshold)
    {
        Name = name ?? String.Empty;
        LogicalOperator = logicalOperator;
        Value = value;
        Threshold = threshold;
    }

    public T Value  { get; }
    public T Threshold  { get; }

    object IMetric.Value =&gt; Value;
    object IMetric.Threshold =&gt; Threshold;

    public string Name  { get; }
    public eLogicalOperator LogicalOperator { get; }

    public bool Faulty()
    {
        //TODO: implement;
    }
}


I don't know, however, how you would implement the Faulty method in a generic way. Maybe you could inject the logic and use a factory method to create metric objects.

Probably a better approach is to create an abstract Metric&lt;T&gt; class having only the Faulty method abstract to be able to implement specialized variants.

public class DecimalMetric : IMetric&lt;decimal&gt;


Yet another, elegant possibility is to pass a delegate doing the comparison, instead of the operator enum.

public Metric(string name, T value, T threshold, Func&lt;T,T,bool&gt; isValid)


This allows you to provide a type specific comparison and saves you a complex processing for different operators. I prefer to ask the positive question "is valid". It feels better than specifying what is wrong. You can call it like this

new Metric&lt;int&gt;("int test", 5, 10, (v, threshold) =&gt; v &lt; threshold)


Usage (still with you original constructor):

var list = new List&lt;IMetric&gt;();
list.Add(new Metric&lt;int&gt;("int", eLogicalOperator.LowerThan, 5, 10));
list.Add(new Metric&lt;double&gt;("double", eLogicalOperator.LowerThan, 2.45, 10.0));

object value = list[0].Value;

switch (list[0]) {
    case Metric&lt;int&gt; intMetric:
        int i = intMetric.Value;
        break;
    case Metric&lt;double&gt; doubleMetric:
        double d = doubleMetric.Value;
        break;
    default:
        break;
}

// or

switch (list[0].Value) { // of type object
    case int i:
        int myInt = i;
        break;
    case double d:
        double myDouble = d;
        break;
    default:
        break;
}