Skip to content

Instantly share code, notes, and snippets.

@ig-sinicyn
Last active October 11, 2016 14:26
Show Gist options
  • Save ig-sinicyn/b4cc36175a002f6c08f2eeb9f35faea2 to your computer and use it in GitHub Desktop.
Save ig-sinicyn/b4cc36175a002f6c08f2eeb9f35faea2 to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Xunit;
using static Xunit.Assert;
namespace DepPropertySample
{
public class Program
{
static void Main(string[] args) => UseCase();
[Fact]
public static void UseCase()
{
var a = new Config()
{
Title = "Hello",
RepeatCount = 123,
AdvancedConfig = new DerivedConfig()
{
RepeatCount = 11
}
};
// 1.1. update unfrozen
a.Title += ", world!!!";
var msg = Throws<InvalidOperationException>(() => a.AdvancedConfig.AdvancedConfig = a.AdvancedConfig);
Equal(msg.Message, "Circular references are not allowed.");
a.AdvancedConfig.AdvancedConfig = new DerivedConfig();
// 1.2. Check properties
Equal(a.Frozen, false);
Equal(a.AdvancedConfig.Frozen, false);
Equal(a.AdvancedConfig.AdvancedConfig.Frozen, false);
Equal(a.RepeatCount, 123);
Equal(a.Title, "Hello, world!!!");
Equal(a.AdvancedConfig.RepeatCount, 11); // overriden
Equal(a.AdvancedConfig.LaunchCount, 111); // fallback value
Equal(a.AdvancedConfig.Title, "Hello, world!!!"); // from parent
Equal(a.AdvancedConfig.AdvancedConfig.Title, "Hello, world!!!"); // from parent
Equal(a.Parent, null);
Equal(a.AdvancedConfig.Parent, a);
Equal(a.AdvancedConfig.AdvancedConfig.Parent, a.AdvancedConfig);
True(a.HasValue(Config.RepeatCountProperty));
True(a.HasValue(Config.TitleProperty));
True(a.AdvancedConfig.HasValue(Config.RepeatCountProperty));
False(a.AdvancedConfig.HasValue(DerivedConfig.LaunchCountProperty));
False(a.AdvancedConfig.HasValue(Config.TitleProperty));
False(a.AdvancedConfig.AdvancedConfig.HasValue(Config.TitleProperty));
// 2.1. Freeze and try update
a.Freeze();
Throws<InvalidOperationException>(() => a.RepeatCount++);
Throws<InvalidOperationException>(() => a.AdvancedConfig.RepeatCount++);
// 2.2. Check properties
Equal(a.Frozen, true);
Equal(a.AdvancedConfig.Frozen, true);
Equal(a.AdvancedConfig.AdvancedConfig.Frozen, true);
Equal(a.RepeatCount, 123);
Equal(a.Title, "Hello, world!!!");
Equal(a.AdvancedConfig.RepeatCount, 11);
Equal(a.AdvancedConfig.LaunchCount, 111); // fallback value
Equal(a.AdvancedConfig.Title, "Hello, world!!!"); // from parent
Equal(a.AdvancedConfig.AdvancedConfig.Title, "Hello, world!!!"); // from parent
Equal(a.Parent, null);
Equal(a.AdvancedConfig.Parent, a);
Equal(a.AdvancedConfig.AdvancedConfig.Parent, a.AdvancedConfig);
True(a.HasValue(Config.RepeatCountProperty));
True(a.HasValue(Config.TitleProperty));
True(a.AdvancedConfig.HasValue(Config.RepeatCountProperty));
False(a.AdvancedConfig.HasValue(DerivedConfig.LaunchCountProperty));
False(a.AdvancedConfig.HasValue(Config.TitleProperty));
False(a.AdvancedConfig.AdvancedConfig.HasValue(Config.TitleProperty));
// 3.1. Unfreeze
var aOld = a;
a = a.UnfreezeCopy();
NotEqual(a, aOld);
// 3.2. Check properties
Equal(a.Frozen, false);
Equal(a.AdvancedConfig.Frozen, false);
Equal(a.AdvancedConfig.AdvancedConfig.Frozen, false);
Equal(a.RepeatCount, 123);
Equal(a.Title, "Hello, world!!!");
Equal(a.AdvancedConfig.RepeatCount, 11);
Equal(a.AdvancedConfig.LaunchCount, 111); // fallback value
Equal(a.AdvancedConfig.Title, "Hello, world!!!"); // from parent
Equal(a.AdvancedConfig.AdvancedConfig.Title, "Hello, world!!!"); // from parent
Equal(a.Parent, null);
Equal(a.AdvancedConfig.Parent, a);
Equal(a.AdvancedConfig.AdvancedConfig.Parent, a.AdvancedConfig);
True(a.HasValue(Config.RepeatCountProperty));
True(a.HasValue(Config.TitleProperty));
True(a.AdvancedConfig.HasValue(Config.RepeatCountProperty));
False(a.AdvancedConfig.HasValue(DerivedConfig.LaunchCountProperty));
False(a.AdvancedConfig.HasValue(Config.TitleProperty));
False(a.AdvancedConfig.AdvancedConfig.HasValue(Config.TitleProperty));
// 4.1. Update unfrozen
a.RepeatCount++;
a.AdvancedConfig.RepeatCount++;
a.AdvancedConfig.Title = "Hello, world V2!!!";
// 4.2. Check properties
Equal(a.Frozen, false);
Equal(a.AdvancedConfig.Frozen, false);
Equal(a.RepeatCount, 124);
Equal(a.Title, "Hello, world!!!");
Equal(a.AdvancedConfig.RepeatCount, 12);
Equal(a.AdvancedConfig.AdvancedConfig.RepeatCount, 12); // from parent
Equal(a.AdvancedConfig.Title, "Hello, world V2!!!"); // overriden
Equal(a.AdvancedConfig.AdvancedConfig.Title, "Hello, world V2!!!"); // from parent
True(a.HasValue(Config.RepeatCountProperty));
Equal(a.Parent, null);
Equal(a.AdvancedConfig.Parent, a);
Equal(a.AdvancedConfig.AdvancedConfig.Parent, a.AdvancedConfig);
True(a.HasValue(Config.TitleProperty));
True(a.AdvancedConfig.HasValue(Config.RepeatCountProperty));
False(a.AdvancedConfig.HasValue(DerivedConfig.LaunchCountProperty));
True(a.AdvancedConfig.HasValue(Config.TitleProperty));
False(a.AdvancedConfig.AdvancedConfig.HasValue(Config.TitleProperty));
}
}
public class Config : DepObject
{
public static readonly DepPropertyKey<int> RepeatCountProperty = DepPropertyKey.Create((Config r) => r.RepeatCount);
public static readonly DepPropertyKey<string> TitleProperty =
DepPropertyKey.Create((Config r) => r.Title);
public static readonly DepPropertyKey<DerivedConfig> AdvancedConfigProperty =
DepPropertyKey.Create((Config r) => r.AdvancedConfig);
public new Config UnfreezeCopy() => UnfreezeCopyCore(this);
public int RepeatCount
{
get { return RepeatCountProperty[this]; }
set { RepeatCountProperty[this] = value; }
}
public string Title
{
get { return TitleProperty[this]; }
set { TitleProperty[this] = value; }
}
public DerivedConfig AdvancedConfig
{
get { return AdvancedConfigProperty[this]; }
set { AdvancedConfigProperty[this] = value; }
}
}
public class DerivedConfig : Config
{
public static readonly DepPropertyKey<int?> LaunchCountProperty = DepPropertyKey.Create(
(DerivedConfig r) => r.LaunchCount, 111);
public new DerivedConfig UnfreezeCopy() => UnfreezeCopyCore(this);
public int? LaunchCount
{
get { return LaunchCountProperty[this]; }
set { LaunchCountProperty[this] = value; }
}
}
public abstract class DepPropertyKey
{
public static readonly object EmptyValue = new object();
public static DepPropertyKey<T> Create<T>(string name) => new DepPropertyKey<T>(name);
public static DepPropertyKey<T> Create<T>(string name, T fallbackValue) => new DepPropertyKey<T>(name, fallbackValue);
public static DepPropertyKey<T> Create<TOwner, T>(Expression<Func<TOwner, T>> name) =>
new DepPropertyKey<T>(((MemberExpression)name.Body).Member.Name);
public static DepPropertyKey<T> Create<TOwner, T>(Expression<Func<TOwner, T>> name, T fallbackValue) =>
new DepPropertyKey<T>(((MemberExpression)name.Body).Member.Name, fallbackValue);
protected DepPropertyKey(string name, object fallbackValue)
{
Name = name;
FallbackValue = fallbackValue;
}
public string Name { get; }
public object FallbackValue { get; }
public object this[DepObject obj]
{
get { return obj.GetValue(this); }
set { obj.SetValue(this, value); }
}
public bool HasValue(DepObject obj) => obj.HasValue(this);
public void ClearValue(DepObject obj) => this[obj] = EmptyValue;
}
public class DepPropertyKey<T> : DepPropertyKey
{
public DepPropertyKey(string name) : base(name, default(T)) { }
public DepPropertyKey(string name, T fallbackValue) : base(name, fallbackValue)
{
FallbackValue = fallbackValue;
}
public new T FallbackValue { get; }
public new T this[DepObject obj]
{
get { return obj.GetValue(this); }
set { obj.SetValue(this, value); }
}
}
public class DepObject
{
// TODO: cache empty dict instance & compare&swap on mutate.
private readonly Dictionary<DepPropertyKey, object> values =
new Dictionary<DepPropertyKey, object>();
private DepObject m_Parent;
private void With(DepObject parent, Dictionary<DepPropertyKey, object> newValues)
{
if (Frozen || Parent != null || values.Any())
throw new InvalidOperationException();
Parent = parent;
foreach (var newValue in newValues)
{
values.Add(newValue.Key, newValue.Value);
}
}
public bool Frozen { get; private set; }
internal DepObject Parent
{
get { return m_Parent; }
set
{
if (Frozen)
throw new InvalidOperationException();
m_Parent = value;
}
}
internal object GetValue(DepPropertyKey key)
{
DepObject current = this;
while (current != null)
{
object result;
if (current.values.TryGetValue(key, out result))
{
return object.ReferenceEquals(result, DepPropertyKey.EmptyValue)
? key.FallbackValue
: result;
}
current = current.Parent;
}
return key.FallbackValue;
}
public bool HasValue(DepPropertyKey key)
{
object result;
if (values.TryGetValue(key, out result))
{
return !object.ReferenceEquals(result, DepPropertyKey.EmptyValue);
}
return false;
}
internal T GetValue<T>(DepPropertyKey<T> key)
{
return (T)GetValue((DepPropertyKey)key);
}
internal void SetValue(DepPropertyKey key, object value)
{
if (Frozen)
throw new InvalidOperationException("Frozen.");
var depObj = value as DepObject;
if (depObj != null)
{
var current = this;
while (current != null)
{
if (object.ReferenceEquals(current, depObj))
throw new InvalidOperationException("Circular references are not allowed.");
current = current.Parent;
}
depObj.Parent = this;
}
values[key] = value;
}
internal void SetValue<T>(DepPropertyKey<T> key, T value) => SetValue((DepPropertyKey)key, value);
public void Freeze()
{
if (Frozen)
return;
Frozen = true;
foreach (var details in values.Values.OfType<DepObject>())
{
details.Freeze();
}
}
public DepObject UnfreezeCopy() => UnfreezeCopyCore(this);
protected static T UnfreezeCopyCore<T>(T instance)
where T : DepObject =>
(T)UnfreezeCopyCore(instance, new Dictionary<DepObject, DepObject>());
private static DepObject UnfreezeCopyCore(
DepObject instance,
Dictionary<DepObject, DepObject> unfrozen)
{
if (instance == null)
return null;
DepObject unfrozenInst;
if (unfrozen.TryGetValue(instance, out unfrozenInst))
throw new InvalidOperationException("Circular reference detected!");
// TODO: emit .ctor delegate
var result = (DepObject)Activator.CreateInstance(instance.GetType());
unfrozen.Add(instance, result);
var newValues = new Dictionary<DepPropertyKey, object>();
foreach (var valuePair in instance.values)
{
var depObj = valuePair.Value as DepObject;
if (depObj == null)
{
newValues.Add(valuePair.Key, valuePair.Value);
}
else
{
newValues.Add(valuePair.Key, UnfreezeCopyCore(depObj, unfrozen));
}
}
result.With(
instance.Parent == null ? null : unfrozen[instance.Parent],
newValues);
return result;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment