| // 1. Вместо старых функций IsNull и IsNotNull делаем новые:
private static BinaryExpression IsNull(Expression expression) {
if(expression == null) {
throw new ArgumentNullException("expression");
}//if
if(IsTypeByReference(expression)) {
return Expression.ReferenceEqual(expression, Null);
} else {
return Expression.Equal(expression, Null);
}//if
}
private static BinaryExpression IsNotNull(Expression expression) {
if(expression == null) {
throw new ArgumentNullException("expression");
}//if
if(IsTypeByReference(expression)) {
return Expression.ReferenceNotEqual(expression, Null);
} else {
return Expression.NotEqual(expression, Null);
}//if
}
private static bool IsTypeByReference(Expression expression) {
if(expression == null) {
throw new ArgumentNullException("expression");
}//if
return IsTypeByReference(expression.Type);
}
private static bool IsTypeNullable(Expression expression) {
if(expression == null) {
throw new ArgumentNullException("expression");
}//if
var type = expression.Type;
return IsTypeByReference(type) || type.IsValueType && Nullable.GetUnderlyingType(type) != null;
}
private static bool IsTypeByReference(Type type) {
if(type == null) {
throw new ArgumentNullException("type");
}//if
return type.IsClass || type.IsInterface;
}
// 2. Метод MakeCompare<P> должен быть таким (изменена ветка else)
protected virtual Expression MakeCompare<P>(Expression left, Expression right, IComparer<P> comparer) {
if(left == null) {
throw new ArgumentNullException("left");
} else if(right == null) {
throw new ArgumentNullException("right");
}//if
if(comparer != null) {
// return comparer.Compare(left, right);
Func<P, P, int> @delegate = comparer.Compare;
var instance = Expression.Constant(comparer);
return Expression.Call(instance, @delegate.Method, left, right);
} else {
// (left < right) ? -1 : (right < left ? 1 : 0);
var compare = Expression.Condition(Expression.LessThan(left, right), MinusOne, Expression.Condition(Expression.LessThan(right, left), One, Zero));
var code = (IsTypeNullable(left) ? 2 : 0) + (IsTypeNullable(right) ? 1 : 0);
switch(code) {
case 0: // "left" and "right" both are not nullable
return compare;
case 1: // "left" is not nullable and "right" is nullable
// (object)right == null ? 1 : {compare};
return Expression.Condition(IsNull(right), One, compare);
case 2: // "left" is nullable and "right" is not nullable
// (object)left == null ? -1 : {compare};
return Expression.Condition(IsNull(left), MinusOne, compare);
case 3: // "left" and "right" both are nullable
// (object)left == null ? (right == null ? 0 : -1) : ((object)right == null ? 1 : {compare});
return Expression.Condition(IsNull(left), Expression.Condition(IsNull(right), Zero, MinusOne), Expression.Condition(IsNull(right), One, compare));
default:
const string Format = "Invalid code: left.Type = {0}, right.Type = {1}, code = {2}.";
var message = String.Format(Format, left.Type, right.Type, code);
Debug.Fail(message);
throw new InvalidOperationException(message);
}//switch
}//if
}
Тест для проверки:
class BaseData
{
public BaseData(DateTime? test = null) {
Test = test;
}
public DateTime? Test { get; private set; }
}
public static void CompareNullableProperty() {
var comparer = new ComparerBuilder<BaseData>().Add(value => value.Test).ToComparer();
var items = new[] {
new BaseData(null),
new BaseData(new DateTime(2015, 01, 01)),
new BaseData(null),
new BaseData(new DateTime(2015, 02, 01)),
new BaseData(null),
new BaseData(new DateTime(2015, 03, 01)),
};
Array.Sort(items, comparer);
var previous = default(BaseData);
foreach(var item in items) {
if(previous != null) {
Debug.Assert(previous.Test == null || previous.Test <= item.Test,String.Format("previous.Test2 == null || previous.Test2 [{0}] <= item.Test2 [{1}]", previous.Test, item.Test));
}//if
previous = item;
}//for
}
|