Как динамически анализировать и сравнивать строковые значения в C #? - PullRequest
1 голос
/ 11 августа 2009

Извините, что мне пришлось отредактировать этот вопрос.

Мне нужно динамически проанализировать два строковых значения для соответствующего типа и сравнить их, а затем вернуть результат bool.

Пример 1:

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

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

Пример 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

В настоящее время я делаю так (думаю, глупо так делать):

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

А это:

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

Я ищу лучшую (более универсальную) реализацию (возможно, с использованием дерева выражений?). Я ценю любые предложения. Спасибо!

Ответы [ 3 ]

3 голосов
/ 13 августа 2009

Самый общий способ - использовать TypeConverter. Допустим, у нас есть:

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

откуда-то. Вы можете сделать:

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
}

К сожалению, нет способа избежать catch при работе с конвертерами. Теперь, когда у вас есть два объекта, проанализированных из строк, вы можете выполнять общие сравнения. Равенство простое - просто используйте Object.Equals (желательно статическое - оно обрабатывает нули):

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

Для упорядочивания сравнений вы можете проверить, поддерживает ли тип IComparable:

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

Обратите внимание, что некоторые типы, которые не предоставляют operator<, все еще упорядочены и, таким образом, реализуют IComparable - например, String делает это.

1 голос
/ 13 августа 2009

О, я собираюсь владеть этой проблемой. :)

Редактировать: Тесты:

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

кишки:

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);
    }
}
0 голосов
/ 13 августа 2009

Это домашняя работа?

Я имею в виду, ответ просто написать реализацию для класса / метода.

Какая конкретно проблема у вас возникла? Что именно вы не понимаете, точно?

Если разбить проблему на части, это станет легко.

...