In Visual Studio Color Theme Editor extension if you change some colors, some other related colors will change accordingly. They can become equal to the changed color or be more lighter/darker for example.
I wondered how can this behavior be implemented as generalized as possible. So, I came up with this crazy solution.
using System;
using System.Collections.Generic;
using System.Drawing;
namespace tester {
interface IKeyGet<TKey, TValue> {
TValue Get(TKey key);
}
interface IKeySet<TKey, TValue> {
void Set(TKey key, TValue value);
}
interface IKeyGetSet<TKey, TValue> : IKeyGet<TKey, TValue>, IKeySet<TKey, TValue> {
}
class SomeManager<TKey, TValue> : IKeyGetSet<TKey, TValue> {
protected ICollection<IKeyGet<TKey, TValue>> Getters { get; set; }
protected ICollection<IKeySet<TKey, TValue>> Setters { get; set; }
public SomeManager(ICollection<IKeyGet<TKey, TValue>> getters, ICollection<IKeySet<TKey, TValue>> setters) {
Getters = getters;
Setters = setters;
}
public virtual TValue Get(TKey key) {
var value = default(TValue);
foreach (var g in Getters) {
value = g.Get(key);
}
return value;
}
public virtual void Set(TKey key, TValue value) {
foreach (var s in Setters) {
s.Set(key, value);
}
}
}
// Gets value by key, throws if key is not found
class BasicGetter<TKey, TValue> : IKeyGet<TKey, TValue> {
public IDictionary<TKey, TValue> Source { get; set; }
public BasicGetter(IDictionary<TKey, TValue> source) {
Source = source;
}
public TValue Get(TKey key) {
return Source[key];
}
}
// Can add new key-value pair or update existing
class CreateUpdateSetter<TKey, TValue> : IKeySet<TKey, TValue> {
public IDictionary<TKey, TValue> Source { get; set; }
public CreateUpdateSetter(IDictionary<TKey, TValue> source) {
Source = source;
}
public void Set(TKey key, TValue value) {
Source[key] = value;
}
}
// Can only update existing key-value pairs, adding new ones is forbidden
class UpdateSetter<TKey, TValue> : IKeySet<TKey, TValue> {
public IDictionary<TKey, TValue> Source { get; set; }
public UpdateSetter(IDictionary<TKey, TValue> source) {
Source = source;
}
public void Set(TKey key, TValue value) {
if (!Source.ContainsKey(key)) {
throw new KeyNotFoundException();
} else {
Source[key] = value;
}
}
}
// If key is in RelatedSetters, then invoke all corresponding delegates and set the keys being returned
class RelatedSetter<TKey, TValue> : IKeySet<TKey, TValue> {
public IDictionary<TKey, IEnumerable<Func<TKey, TValue, KeyValuePair<TKey, TValue>>>> RelatedSetters { get; set; }
public IKeySet<TKey, TValue> Source { get; set; }
public RelatedSetter(IKeySet<TKey, TValue> source) {
Source = source;
RelatedSetters = new Dictionary<TKey, IEnumerable<Func<TKey, TValue, KeyValuePair<TKey, TValue>>>>();
}
public void Set(TKey key, TValue value) {
IEnumerable<Func<TKey, TValue, KeyValuePair<TKey, TValue>>> related;
if (RelatedSetters.TryGetValue(key, out related)) {
foreach (var f in related) {
var c = f(key, value);
Source.Set(c.Key, c.Value);
}
}
}
}
// Has read-update behavior
class ColorManager<T> : SomeManager<T, Color> {
private IDictionary<T, Color> _colorMap;
private RelatedSetter<T, Color> _relatedSetter;
public IEnumerable<T> Keys { get { return _colorMap.Keys; } }
public IDictionary<T, IEnumerable<Func<T, Color, KeyValuePair<T, Color>>>> RelatedColors {
get {
return _relatedSetter.RelatedSetters;
}
set {
_relatedSetter.RelatedSetters = value;
}
}
public ColorManager(IDictionary<T, Color> initialColors) :
base(new List<IKeyGet<T, Color>>(), new List<IKeySet<T, Color>>()) {
_colorMap = initialColors;
_relatedSetter = new RelatedSetter<T, Color>(this);
Getters.Add(new BasicGetter<T, Color>(_colorMap));
Setters.Add(new UpdateSetter<T, Color>(_colorMap));
Setters.Add(_relatedSetter);
}
}
class Program {
enum AvailableColors { Water, Submarine, Seabed };
static void Main(string[] args) {
var cm = new ColorManager<AvailableColors>(new Dictionary<AvailableColors, Color> {
{ AvailableColors.Water, Color.Green },
{ AvailableColors.Submarine, Color.Red },
{ AvailableColors.Seabed, Color.Yellow }
});
cm.RelatedColors = new Dictionary<AvailableColors, IEnumerable<Func<AvailableColors, Color, KeyValuePair<AvailableColors, Color>>>> {
{
AvailableColors.Water, new List<Func<AvailableColors, Color, KeyValuePair<AvailableColors, Color>>> {
(k, v) => { return new KeyValuePair<AvailableColors, Color>(AvailableColors.Submarine, Color.Azure); },
(k, v) => { return new KeyValuePair<AvailableColors, Color>(AvailableColors.Seabed, Color.Red); }
}
},
{
AvailableColors.Seabed, new List<Func<AvailableColors, Color, KeyValuePair<AvailableColors, Color>>> {
(k, v) => { return new KeyValuePair<AvailableColors, Color>(AvailableColors.Submarine, Color.Black); },
}
}
};
cm.Set(AvailableColors.Water, Color.AliceBlue);
// Expected colors:
//
// Water - AliceBlue
// Submarine - Black
// Seabed - Red
foreach (var key in cm.Keys) {
Console.WriteLine($"{key}: {cm.Get(key)}");
}
}
}
}
The core idea is to take Dictionary-like class, that can get and set values by keys, and enhance its getters and setters with custom behavior. I think this can be accomplished better with mixins, like in Python, but as C# does not support mixins, collections of functions replace them.
For me, benefit of this solution is behavior customization while retaining simple Get/Set interface.
Although, I'm a little scared of intimidating lines of code like:
public IDictionary<TKey, IEnumerable<Func<TKey, TValue, KeyValuePair<TKey, TValue>>>> RelatedSetters { get; set; }
and
cm.RelatedColors = new Dictionary<AvailableColors, IEnumerable<Func<AvailableColors, Color, KeyValuePair<AvailableColors, Color>>>> {...};
whereas in Python it would be just
cm.RelatedColors = {...}
EDIT:
My question is: "How would you implement collection, in which elements may be related to each other? Such as editing one element may trigger changes in one or more of other elements."
No need for concrete examples, theories will be enough.