views:

587

answers:

3

Sorry that I had to re-edit this question.

I need to dynamically parse two string values to the appropriate type and compare them, then return a bool result.

Example 1:

string lhs = “10”;
string rhs = “10”;

Compare.DoesEqual(lhs, rhs, typeof(int)); //true
Compare.DoesEqual(lhs, rhs, typeof(string)); //true

Example 2:

string lhs = “2.0”;
string rhs = “3.1”;

Compare.IsGreaterThan(lhs, rhs, typeof(int)); //false
Compare.IsGreaterThan(lhs, rhs, typeof(double)); //false
Compare.IsGreaterThan(lhs, rhs, typeof(string)); //invalid, always false

Currently I am doing like this (I think it's stupid to do it such way):

public partial class Comparer
{
    public static bool DoesEqual(string lhs, string rhs, Type type)
    {
        if (type.Equals(typeof(int)))
        {
            try
            {
                return int.Parse(lhs) > int.Parse(rhs);
            }
            catch
            {
                return false;
            }
        }

        if (type.Equals(typeof(double)))
        {
            try
            {
                return double.Parse(lhs) > double.Parse(rhs);
            }
            catch
            {
                return false;
            }
        }

        return false;
    }
}

And this:

public partial class Comparer
{
    public static bool IsGreaterThan(string lhs, string rhs, Type type)
    {
        if (type.Equals(typeof(int)))
        {
            try
            {
                return int.Parse(lhs) == int.Parse(rhs);
            }
            catch
            {
                return false;
            }
        }

        if (type.Equals(typeof(double)))
        {
            try
            {
                return double.Parse(lhs) == double.Parse(rhs);
            }
            catch
            {
                return false;
            }
        }

        if (type.Equals(typeof(string)))
        {
            return lhs.Equals(rhs);
        }

        return false;
    }
}

I am looking for better a better (more generic way) implementation (perhaps using Expression Tree?). I appreciate any suggestions. Thank you!

A: 

Is this homework?

I mean, the answer is to just write the implementation for the class/method.

What is the specific problem you are having? What is it you don't understand, exactly?

If you break the problem down into parts it becomes easy.

Noon Silk
This is not homework and I am looking for a more generic way to do this rather than implement each thing individually. May I should take a look at this http://www.yoda.arachsys.com/csharp/miscutil/ there’s a generic operator class but it doesn’t work on object type.
Jeffrey C
I think the problem can be “How to write a generic converter that dynamically converts a string value to a given type and return the correct type without casting.” Otherwise as you said I have to write each individual implementation by hand.
Jeffrey C
Well you'd not have 'operator' as a string, you'd have it as a class, perhaps extending some base class and a method called 'Operate'. But it'd have to take a 'params object[] foo' as input, due to the various ways it could 'operate'.It's a little strange, really.
Noon Silk
+2  A: 

The most generic way is to use TypeConverter. Let's say we have:

string rhs = ...;
string lhs = ...;
Type type = ...;

from somewhere. You can do:

TypeConverter conv = TypeDescriptor.GetConverter(type);
try
{
    object rho = conv.ConvertFrom(rhs);
    object lho = conv.ConvertFrom(lhs);
    ...
}
catch (NotSupportedException)
{
   // No luck - couldn't parse the string
}

Unfortunately, there's no way to avoid a catch there when dealing with converters. Now that you have two objects parsed from strings, you can do generic comparisons. Equality is simple - just use Object.Equals (preferably static one - it handles nulls):

if (Object.Equals(rho, lho))
{
    ...
}

For ordering comparisons, you can check if the type supports IComparable:

IComparable rhc = rho as IComparable;
if (rhc != null && rhc.CompareTo(lho) < 0) // rhs less than lhs
{ 
    ...
}

Note however that some types that don't provide operator< are still ordered, and thus implement IComparable - for example, String does that.

Pavel Minaev
+1  A: 

Oh I am so about to own this problem. :)

Edit: The tests:

[TestMethod]
public void TestDynamicComparer()
{
    string lhs = "10";
    string rhs = "10";
    Assert.AreEqual(0, DynamicComparer<int>.Default.Compare(lhs, rhs));

    lhs = "2.0";
    rhs = "3.1";
    // basic equality
    Assert.AreNotEqual(0, DynamicComparer<double>.Default.Compare(lhs, rhs));
    // correct order
    Assert.IsTrue(DynamicComparer<double>.Default.Compare(lhs, rhs) < 0);
    // check two invalid casts are unordered
    Assert.AreEqual(0, DynamicComparer<int>.DefaultNoThrow.Compare(lhs, rhs));

    // real proof it works
    lhs = "9";
    rhs = "09";
    Assert.AreEqual(0, DynamicComparer<int>.Default.Compare(lhs, rhs));

    lhs = "9.0";
    rhs = "09";
    Assert.AreEqual(0, DynamicComparer<double>.Default.Compare(lhs, rhs));
    // test the valid cast is ordered ahead of ("less than") the invalid cast
    Assert.AreNotEqual(0, DynamicComparer<int>.DefaultNoThrow.Compare(lhs, rhs));
    Assert.IsTrue(DynamicComparer<int>.DefaultNoThrow.Compare(lhs, rhs) > 0);
}

[TestMethod]
[ExpectedException(typeof(InvalidCastException))]
public void TestDynamicComparerInvalidCast()
{
    // make sure the default comparer throws an InvalidCastException if a cast fails
    string lhs = "2.0";
    string rhs = "3.1";
    DynamicComparer<int>.Default.Compare(lhs, rhs);
}

The guts:

public class DynamicComparer<T>
    : IComparer<string>
    , IEqualityComparer<string>
{
    private static readonly DynamicComparer<T> defaultComparer = new DynamicComparer<T>();
    private static readonly DynamicComparer<T> defaultNoThrowComparer = new DynamicComparer<T>(false);

    private DynamicComparerHelper.TryParseDelegate<T> parser = DynamicComparerHelper.GetParser<T>();
    private IComparer<T> comparer;
    private bool throwOnError;

    public DynamicComparer()
        : this(Comparer<T>.Default, true)
    {
    }

    public DynamicComparer(bool throwOnError)
        : this(Comparer<T>.Default, throwOnError)
    {
    }

    public DynamicComparer(IComparer<T> comparer)
        : this(comparer, true)
    {
    }

    public DynamicComparer(IComparer<T> comparer, bool throwOnError)
    {
        this.comparer = comparer;
        this.throwOnError = throwOnError;
    }

    public static DynamicComparer<T> Default
    {
        get
        {
            return defaultComparer;
        }
    }

    public static DynamicComparer<T> DefaultNoThrow
    {
        get
        {
            return defaultNoThrowComparer;
        }
    }

    public int Compare(string x, string y)
    {
        T valueX;
        T valueY;

        bool convertedX = this.parser(x, out valueX);
        bool convertedY = this.parser(y, out valueY);

        if (this.throwOnError && !(convertedX && convertedY))
            throw new InvalidCastException();

        if (!(convertedX || convertedY))
            return 0;

        if (!convertedX)
            return 1;

        if (!convertedY)
            return -1;

        return this.comparer.Compare(valueX, valueY);
    }

    public bool Equals(string x, string y)
    {
        return Compare(x, y) == 0;
    }

    public int GetHashCode(string x)
    {
        T value;
        bool converted = this.parser(x, out value);

        if (this.throwOnError && !converted)
            throw new InvalidCastException();

        if (!converted)
            return 0;

        return value.GetHashCode();
    }
}

internal class DynamicComparerHelper
{
    public delegate bool TryParseDelegate<T>(string text, out T value);

    private static readonly Dictionary<Type, Delegate> converters =
        new Dictionary<Type, Delegate>()
        {
            { typeof(bool), WrapDelegate<bool>(bool.TryParse) },
            { typeof(short), WrapDelegate<short>(short.TryParse) },
            { typeof(int), WrapDelegate<int>(int.TryParse) },
            { typeof(long), WrapDelegate<long>(long.TryParse) },
            { typeof(ushort), WrapDelegate<ushort>(ushort.TryParse) },
            { typeof(uint), WrapDelegate<uint>(uint.TryParse) },
            { typeof(ulong), WrapDelegate<ulong>(ulong.TryParse) },
            { typeof(float), WrapDelegate<float>(float.TryParse) },
            { typeof(double), WrapDelegate<double>(double.TryParse) },
            { typeof(DateTime), WrapDelegate<DateTime>(DateTime.TryParse) },
        };

    public static TryParseDelegate<T> GetParser<T>()
    {
        return (TryParseDelegate<T>)converters[typeof(T)];
    }

    private static TryParseDelegate<T> WrapDelegate<T>(TryParseDelegate<T> parser)
    {
        return new TryParseDelegate<T>(parser);
    }
}
280Z28
Thank you. Although your solution is more generic but in my specific case http://stackoverflow.com/questions/1258181/how-to-dynamically-parse-and-compare-string-values-in-c/1270205#1270205 is more suitable. But I am sure I will use your solution in the not near future. Thank you again.
Jeffrey C