Last active
August 29, 2015 14:00
-
-
Save SlyNet/11223318 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public static class Monads | |
{ | |
/// <summary> | |
/// Returns the value of an expression, or <c>default(T)</c> if any parts of the expression are <c>null</c>. | |
/// </summary> | |
/// <typeparam name="T">The type of the Expression</typeparam> | |
/// <param name="expression">A parameterless lambda representing the path to the value.</param> | |
/// <returns>The value of the expression, or <c>default(T)</c> if any parts of the expression are <c>null</c>.</returns> | |
public static T Maybe<T>(Expression<Func<T>> expression) | |
{ | |
var value = Maybe(expression.Body); | |
if (value == null) return default(T); | |
return (T)value; | |
} | |
private static object Maybe(Expression expression) | |
{ | |
var constantExpression = expression as ConstantExpression; | |
if (constantExpression != null) | |
{ | |
return constantExpression.Value; | |
} | |
var memberExpression = expression as MemberExpression; | |
if (memberExpression != null) | |
{ | |
var memberValue = Maybe(memberExpression.Expression); | |
if (memberValue != null) | |
{ | |
var member = memberExpression.Member; | |
return GetValue(member, memberValue); | |
} | |
} | |
var methodCallExpression = expression as MethodCallExpression; | |
if (methodCallExpression != null) | |
{ | |
var methodValue = Maybe(methodCallExpression.Object); | |
var arguments = methodCallExpression.Arguments.Select(Maybe).ToArray(); | |
return GetValue(methodCallExpression.Method, methodValue, arguments); | |
} | |
var unaryExpression = expression as UnaryExpression; | |
if (unaryExpression != null && unaryExpression.NodeType == ExpressionType.Convert) | |
{ | |
var result = Maybe(unaryExpression.Operand); | |
if (result != null && IsNullableType(unaryExpression.Type)) | |
{ | |
return Activator.CreateInstance(unaryExpression.Type, result); | |
} | |
return result; | |
} | |
var binaryExpression = expression as BinaryExpression; | |
if (binaryExpression != null && binaryExpression.NodeType == ExpressionType.Coalesce) | |
{ | |
return Maybe(binaryExpression.Left) ?? Maybe(binaryExpression.Right); | |
} | |
return null; | |
} | |
private static object GetValue(MemberInfo member, object memberValue, object[] arguments = null) | |
{ | |
var propertyInfo = member as PropertyInfo; | |
if (propertyInfo != null) | |
{ | |
return propertyInfo.GetValue(memberValue, null); | |
} | |
var fieldInfo = member as FieldInfo; | |
if (fieldInfo != null) | |
{ | |
return fieldInfo.GetValue(memberValue); | |
} | |
var methodInfo = member as MethodInfo; | |
if (methodInfo != null && (memberValue != null || methodInfo.IsStatic)) | |
{ | |
return methodInfo.Invoke(memberValue, arguments); | |
} | |
return null; | |
} | |
private static bool IsNullableType(Type theType) | |
{ | |
return (theType.IsGenericType && theType.GetGenericTypeDefinition() == typeof(Nullable<>)); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[TestFixture] | |
public class MonadsTests | |
{ | |
[Test] | |
public void should_work_correctly_with_methods() | |
{ | |
var testVariable = new ClassWithMethods(); | |
long x = testVariable.Get(3).X; | |
Assert.That(x, Is.EqualTo(3)); | |
x = Monads.Maybe(() => testVariable.Get(3).X); | |
Assert.That(x, Is.EqualTo(3)); | |
x = Monads.Maybe(() => testVariable.GetDefault().X); | |
Assert.That(x, Is.EqualTo(42)); | |
x = Monads.Maybe(() => testVariable.GetItself().Get(100500).X); | |
Assert.That(x, Is.EqualTo(100500)); | |
x = Monads.Maybe(() => testVariable.GetItself().GetNull().X); | |
Assert.That(x, Is.EqualTo(o)); | |
} | |
[Test] | |
public void should_work_correctly_with_nullables() | |
{ | |
var testVariable = new ClassWithNestedObject(); | |
long x = Monads.Maybe(() => testVariable.Field.X); | |
Assert.That(x, Is.EqualTo(o)); | |
var nullableX = Monads.Maybe<long?>(() => testVariable.Field.X); | |
Assert.That(nullableX, Is.Null); | |
testVariable.Field = new NestedClass | |
{ | |
X = 3 | |
}; | |
x = Monads.Maybe(() => testVariable.Field.X); | |
Assert.That(x, Is.EqualTo(3)); | |
nullableX = Monads.Maybe<long?>(() => testVariable.Field.X); | |
Assert.That(nullableX, Is.EqualTo(3)); | |
} | |
[Test] | |
public void should_work_correctly_with_null_coalesce_operator() | |
{ | |
int? nullValue = null; | |
int? notNullValue = 3; | |
var nullResult = Monads.Maybe(() => nullValue ?? nullValue); | |
Assert.That(nullResult, Is.EqualTo(nullValue)); | |
var notNullResultFirst = Monads.Maybe(() => notNullValue ?? nullValue); | |
Assert.That(notNullResultFirst, Is.EqualTo(notNullValue)); | |
var notNullResultSecond = Monads.Maybe(() => nullValue ?? notNullValue); | |
Assert.That(notNullResultSecond, Is.EqualTo(notNullValue)); | |
int? anotherNotNullValue = 5; | |
var anotherNotNullResult = Monads.Maybe(() => anotherNotNullValue ?? notNullValue); | |
Assert.That(anotherNotNullResult, Is.EqualTo(anotherNotNullValue)); | |
} | |
[Test] | |
public void Should_return_null_for_nullable_types_with_null_value_When_called_method_on_them() | |
{ | |
var testClass = new NestedClass(); | |
string result = Monads.Maybe(() => testClass.Y.ToString()); | |
Assert.That(result, Is.Null); | |
} | |
[Test] | |
public void Should_support_static_methods_wrap() | |
{ | |
var result= Monads.Maybe(() => string.Format("{0}", "42")); | |
Assert.That(result, Is.EqualTo("42"), "Expression inside monad with static method should be called and return result"); | |
} | |
[Test] | |
public void Should_support_extension_methods() | |
{ | |
// arrange | |
CaseWorker caseWorker = Create.ProcessModel.CaseWorker().WithAgent(Create.UserAgent().Build()).Build(); | |
// act | |
IUserAware userAware = Monads.Maybe(() => caseWorker.As<IUserAware>()); | |
// assert | |
Assert.That(userAware, Is.Not.Null, "'As' extension call should return value"); | |
} | |
[Test] | |
public void Should_support_cast_expressions() | |
{ | |
var result = Monads.Maybe(() => string.Format("{0}", 42)); | |
Assert.That(result, Is.EqualTo("42"), "Expression inside monad with static method should be called and return result"); | |
} | |
private class NestedClass | |
{ | |
public long X { get; set; } | |
public long? Y { get; set; } | |
} | |
private class ClassWithNestedObject | |
{ | |
public NestedClass Field { get; set; } | |
} | |
private class ClassWithMethods | |
{ | |
public NestedClass Get(long x) | |
{ | |
return new NestedClass | |
{ | |
X = x | |
}; | |
} | |
public NestedClass GetDefault() | |
{ | |
return new NestedClass | |
{ | |
X = 42 | |
}; | |
} | |
public ClassWithMethods GetItself() | |
{ | |
return this; | |
} | |
public NestedClass GetNull() | |
{ | |
return null; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment