Skip to content

Instantly share code, notes, and snippets.

@eouw0o83hf
Created August 13, 2018 14:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eouw0o83hf/2581f240af679ddd4870ad9f1642771e to your computer and use it in GitHub Desktop.
Save eouw0o83hf/2581f240af679ddd4870ad9f1642771e to your computer and use it in GitHub Desktop.
Change: A C# handler for null-safe change handling
using System;
using Newtonsoft.Json;
namespace eouw0o83hf.Gist.Change
{
/// <summary>
/// A lightweight wrapper which tracks a change to a property
/// and serializes to one json property for ease of reading in
/// an event stream.
/// </summary>
/// <remarks>
/// How this only serializes to one field - it needs to be broken
/// down by use case for struct Ts (non-nullable) and class Ts
/// (nullable):
///
/// Struct:
/// If a change occurs, Value **will have a value** and Unset will
/// be false, which returns null (and JSON serializes to nothing).
/// If no change occurs, this entire object will be null.
///
/// Class:
/// If a change occurs:
/// If Value is null, Unset should be set to true. In this
/// case, the serializer will ignore Value and only render
/// the Unset property.
/// If Value has a value, Unset will be false, which returns
/// null, and JSON serializes it to nothing.
/// If no change occurs, this entire object will be null.
/// </remarks>
public class Change<T>
{
[JsonIgnore]
private T _value;
[JsonProperty("value")]
public T Value
{
get => _value;
set
{
_value = value;
_unset = false;
}
}
[JsonIgnore]
private bool _unset;
/// <summary>
/// Denotes that the target property should be set to null.
/// Returns true or null to make serialization prettier.
/// </summary>
[JsonProperty("unset")]
public bool? Unset
{
get => _unset ? true : (bool?)null;
set => _unset = value ?? false;
}
// Make model binding easy by directly binding given values
// of T to Change<T>s which wrap them
public static implicit operator Change<T>(T value) => new Change<T> { Value = value };
// I don't think we need this
// public static explicit operator T(Change<T> value) => value.Value;
// Make it easy to apply a Change<T> to a model. Use:
// myProperty |= myPropertyChange;
public static T operator |(T left, Change<T> right)
=> right != null ? right.Value : left;
}
}
using Fermion.Web.Events;
using Xunit;
namespace eouw0o83hf.Gist.Tests.Change
{
public class ChangeTests
{
public class GivenStruct
{
[Fact]
public void GivenValue_ShouldShowChange()
{
var sut = new Change<int> { Value = 1 };
Assert.Equal(1, sut.Value);
Assert.Null(sut.Unset);
}
[Fact]
public void GivenValue_UsingImplicitConversion_ShouldShowChange()
{
Change<int> sut = 1;
Assert.Equal(1, sut.Value);
Assert.Null(sut.Unset);
}
[Fact]
public void GivenOrEquals_AndNoChange_ShouldKeepOriginalValue()
{
var a = 1;
Change<int> sut = null;
a |= sut;
Assert.Equal(1, a);
}
[Fact]
public void GivenOrEquals_AndChange_ShouldChangeValue()
{
var a = 1;
var sut = new Change<int> { Value = 2 };
a |= sut;
Assert.Equal(2, a);
}
}
public class GivenClass
{
[Fact]
public void GivenValue_UsingImplicitConversion_ShouldShowChange()
{
Change<int?> sut = 1;
Assert.Null(sut.Unset);
Assert.Equal(1, sut.Value);
}
[Fact]
public void GivenOrEquals_AndNoChange_ShouldKeepOriginalValue()
{
int? a = 1;
Change<int?> sut = null;
a |= sut;
Assert.Equal(1, a);
}
[Fact]
public void GivenOrEquals_AndChangeWithNullValue_ShouldChangeValue()
{
int? a = 1;
var sut = new Change<int?> { Unset = true };
a |= sut;
Assert.Equal(null, a);
}
[Fact]
public void GivenOrEquals_AndChangeWithValue_AndNullLeft_ShouldChangeValue()
{
int? a = null;
var sut = new Change<int?>{ Value = 1 };
a |= sut;
Assert.Equal(1, a);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment