Jeff
Jeff

Reputation: 13199

How to dynamically parse and compare string values in C#?

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!

Upvotes: 1

Views: 4061

Answers (3)

Sam Harwell
Sam Harwell

Reputation: 99889

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);
    }
}

Upvotes: 2

Pavel Minaev
Pavel Minaev

Reputation: 101595

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.

Upvotes: 3

Noon Silk
Noon Silk

Reputation: 55092

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.

Upvotes: -2

Related Questions