Skip to content

Instantly share code, notes, and snippets.

@josheinstein
Created May 25, 2012 18:50
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save josheinstein/2789805 to your computer and use it in GitHub Desktop.
Save josheinstein/2789805 to your computer and use it in GitHub Desktop.
Handling "null" values in a more dynamic fashion in C#
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Dynamic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
namespace Einstein.Dynamic
{
/// <summary>
/// A special null indicator that can be used in place of null values in a dynamic context
/// which enhances the way nulls normally behave. For example, null reference exceptions
/// are not thrown when attempting to access properties of the null value. Any attempts to
/// set members of the null value or invoke methods on the null value will throw a
/// NullReferenceException.
/// See the example code for more information.
/// </summary>
/// <example>
/// // start with a value of type DynamicNull
/// // it must use the dynamic keyword.
/// // this value can be returned by other dynamic types in place of nulls
/// // see DynamicDictionaryAdapter for an example in action
/// dynamic x = DynamicNull.Value;
///
/// if ( x == null ) { Console.WriteLine("x is null!"); }
/// if ( x == DBNull.Value ) { Console.WriteLine("Also considered equal to DBNull!"); }
/// if ( !x ) { Console.WriteLine("x is false!"); }
/// if ( x ) { Console.WriteLine("x is not true so you won't see this."); }
///
/// int? y = x; // y is now null
/// int z = x; // z is now zero
/// </example>
public sealed class DynamicNull : DynamicObject
{
#region Fields
/// <summary>
/// A singleton instance of a null object that does not throw exceptions upon accessing
/// its non-existing members.
/// </summary>
public static readonly dynamic Value = new DynamicNull( );
#endregion
#region Constructors
private DynamicNull( )
{
}
#endregion
#region Methods
public static bool IsNull( dynamic obj )
{
if ( ReferenceEquals( obj, null ) ) {
return true;
}
if ( ReferenceEquals( obj, Type.Missing ) ) {
return true;
}
Type objType = obj.GetType( );
if ( objType == typeof( DynamicNull ) ) {
return true;
}
if ( objType == typeof( DBNull ) ) {
return true;
}
if ( objType == typeof( Missing ) ) {
return true;
}
return false;
}
public override bool Equals( object obj )
{
return IsNull( obj );
}
public override int GetHashCode( )
{
return 0;
}
public override IEnumerable<string> GetDynamicMemberNames( )
{
yield break;
}
public override bool TryBinaryOperation( BinaryOperationBinder binder, object arg, out object result )
{
WriteDebug( "[{0}] TryBinaryOperation(Operation={1})", binder.ReturnType, binder.Operation );
switch ( binder.Operation ) {
case ExpressionType.Equal: {
result = Equals( arg );
return true;
}
case ExpressionType.NotEqual: {
result = !Equals( arg );
return true;
}
case ExpressionType.GreaterThan:
case ExpressionType.GreaterThanOrEqual:
case ExpressionType.LessThan:
case ExpressionType.LessThanOrEqual: {
result = false;
return true;
}
}
result = this;
return true;
}
public override bool TryConvert( ConvertBinder binder, out object result )
{
WriteDebug( "[{0}] TryConvert(Type={1}, Explicit={2})", binder.ReturnType, binder.Type, binder.Explicit );
if ( binder.Type == typeof( System.Collections.IEnumerable ) ) {
result = Enumerable.Empty<object>( );
return true;
}
if ( binder.Type == typeof( System.Collections.IEnumerator ) ) {
result = Enumerable.Empty<object>( ).GetEnumerator( );
return true;
}
if ( binder.Type.IsGenericType && binder.Type.GetGenericTypeDefinition( ) == typeof( Nullable<> ) ) {
result = null;
return true;
}
if ( binder.Type.IsValueType ) {
result = Activator.CreateInstance( binder.Type );
return true;
}
result = null;
return true;
}
public override bool TryGetIndex( GetIndexBinder binder, object[] indexes, out object result )
{
WriteDebug( "[{0}] TryGetIndex(Index={1})", binder.ReturnType, indexes[0] );
result = this;
return true;
}
public override bool TryGetMember( GetMemberBinder binder, out object result )
{
WriteDebug( "[{0}] TryGetMember(Name={1})", binder.ReturnType, binder.Name );
result = this;
return true;
}
public override bool TryInvoke( InvokeBinder binder, object[] args, out object result )
{
WriteDebug( "[{0}] TryInvoke()", binder.ReturnType );
throw new NullReferenceException( );
}
public override bool TryInvokeMember( InvokeMemberBinder binder, object[] args, out object result )
{
WriteDebug( "[{0}] TryInvokeMember(Name={1})", binder.ReturnType, binder.Name );
throw new NullReferenceException( );
}
public override bool TrySetIndex( SetIndexBinder binder, object[] indexes, object value )
{
WriteDebug( "[{0}] TrySetIndex(Index={1})", binder.ReturnType, indexes[0] );
throw new NullReferenceException( );
}
public override bool TrySetMember( SetMemberBinder binder, object value )
{
WriteDebug( "[{0}] TrySetMember(Name={1})", binder.ReturnType, binder.Name );
throw new NullReferenceException( );
}
public override bool TryUnaryOperation( UnaryOperationBinder binder, out object result )
{
WriteDebug( "[{0}] TryUnaryOperation(Operation={1})", binder.ReturnType, binder.Operation );
switch ( binder.Operation ) {
case ExpressionType.IsTrue: {
result = false;
return true;
}
case ExpressionType.IsFalse: {
result = true;
return true;
}
case ExpressionType.Not: {
result = true;
return true;
}
}
result = this;
return true;
}
private static void WriteDebug( string message, params object[] args )
{
Debugger.Log( 0, "DynamicNullObject", String.Format( message, args ) + Environment.NewLine );
}
#endregion
#region Operators
public static bool operator ==( DynamicNull obj, object other )
{
WriteDebug( "Operator ==" );
return IsNull( other );
}
public static bool operator !=( DynamicNull obj, object other )
{
WriteDebug( "Operator !=" );
return !IsNull( other );
}
#endregion
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CSharp.RuntimeBinder;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Einstein.Dynamic
{
[TestClass]
public class DynamicNullTests
{
[TestMethod]
public void DynamicNull_EqualsNull( )
{
dynamic x = DynamicNull.Value;
dynamic y = null;
Assert.IsTrue( x == y );
}
[TestMethod]
public void DynamicNull_IsNotTrue( )
{
dynamic x = DynamicNull.Value;
if ( x ) {
// this should not happen
Assert.Fail( "Derp!" );
}
}
[TestMethod]
public void DynamicNull_IsFalse( )
{
dynamic x = DynamicNull.Value;
if ( !x ) {
Assert.IsTrue( true, "this is ok!" );
}
else {
// this should not happen
Assert.Fail( "It was supposed to be false. :(" );
}
}
[TestMethod]
public void DynamicNull_CastsToNullableValueType( )
{
dynamic x = DynamicNull.Value;
int? y = x;
Assert.AreEqual( null, y );
}
[TestMethod]
public void DynamicNull_CastsToDefaultValueType( )
{
dynamic x = DynamicNull.Value;
int y = x;
Assert.AreEqual( 0, y );
}
[TestMethod]
public void DynamicNull_NotGreaterOrLessThanAnything( )
{
dynamic x = DynamicNull.Value;
Assert.IsFalse( x > Int32.MinValue );
Assert.IsFalse( x > Int32.MaxValue );
Assert.IsFalse( x > default( Int32 ) );
Assert.IsFalse( x >= Int32.MinValue );
Assert.IsFalse( x >= Int32.MaxValue );
Assert.IsFalse( x >= default( Int32 ) );
Assert.IsFalse( x < Int32.MinValue );
Assert.IsFalse( x < Int32.MaxValue );
Assert.IsFalse( x < default( Int32 ) );
Assert.IsFalse( x <= Int32.MinValue );
Assert.IsFalse( x <= Int32.MaxValue );
Assert.IsFalse( x <= default( Int32 ) );
}
[TestMethod]
public void DynamicNull_CoalescesPropertyAccess( )
{
dynamic x = DynamicNull.Value;
dynamic y = x.i.donot.exist;
Assert.IsTrue( y == null );
}
[TestMethod]
[ExpectedException( typeof( NullReferenceException ) )]
public void DynamicNull_ThrowsOnSetMember( )
{
dynamic x = DynamicNull.Value;
x.derp = "derp";
}
[TestMethod]
[ExpectedException( typeof( NullReferenceException ) )]
public void DynamicNull_ThrowsOnSetIndex( )
{
dynamic x = DynamicNull.Value;
x["derp"] = "derp";
}
[TestMethod]
[ExpectedException( typeof( NullReferenceException ) )]
public void DynamicNull_ThrowsOnInvokeMember( )
{
dynamic x = DynamicNull.Value;
x.derp( );
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment