Created
September 3, 2019 18:39
-
-
Save pmunin/5a1b43d7d4c11df6f4553403e2981a6b to your computer and use it in GitHub Desktop.
Dynamic wrapper for Dictonary
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
using System; | |
using System.Collections.Generic; | |
using System.Dynamic; | |
using System.Linq; | |
public class DynamicDictionaryWrapper<TValue> : DynamicObject | |
{ | |
/// <summary> | |
/// Instance of object passed in | |
/// </summary> | |
public IDictionary<string, TValue> Source { get; } | |
public bool ThrowIfKeyNotFound { get; } | |
public bool? ThrowIfKeyNotFoundSetter { get; } = null; | |
//public Func<string, string> GetMemberNameByColumn { get; } | |
public static IDictionary<string,TValue> GetSource(dynamic dynamicRow) | |
{ | |
var dr = dynamicRow as DynamicDictionaryWrapper<TValue>; | |
return dr.Source; | |
} | |
/// <summary> | |
/// Pass in a source dictionary | |
/// </summary> | |
/// <param name="source"></param> | |
/// <param name="throwIfKeyNotFound"> | |
/// for situation when column does not exist in row | |
/// if this option true then it will throw error, otherwise will return null | |
/// </param> | |
/// <param name="getMemberNameByColumn">generates member name by column name. If not specified StringEscapeExtensions.EscapeVariableName is used</param> | |
/// <param name="throwIfKeyNotFoundSetter"> | |
/// throw if key not found even for setter. If not specified - uses throwIfKeyNotFound | |
/// </param> | |
public DynamicDictionaryWrapper(IDictionary<string,TValue> source, bool throwIfKeyNotFound = false, Func<string, string> getMemberNameByColumn = null, bool? throwIfKeyNotFoundSetter = null) | |
{ | |
Source = source; | |
ThrowIfKeyNotFound = throwIfKeyNotFound; | |
ThrowIfKeyNotFoundSetter = throwIfKeyNotFoundSetter; | |
var cache = source.GetOrAddWeakData("MemberNameToColumn", _ => | |
{ | |
if (getMemberNameByColumn == null) | |
getMemberNameByColumn = StringEscapeExtensions.EscapeVariableName; | |
return source.Keys | |
.Select(c => new { Column = c, Member = getMemberNameByColumn(c) }) | |
.Aggregate(new { ColumnByMember = new Dictionary<string, string>(), MemberByColumn = new Dictionary<string, string>() } | |
, args => { | |
args.Result.ColumnByMember.Add(args.Item.Member, args.Item.Column); | |
args.Result.MemberByColumn.Add(args.Item.Column, args.Item.Member); | |
}); | |
}); | |
this.KeyByMember = cache.ColumnByMember; | |
this.MemberByKey = cache.MemberByColumn; | |
} | |
public Dictionary<string, string> KeyByMember { get; } | |
public Dictionary<string, string> MemberByKey { get; } | |
public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) | |
{ | |
var throwIfNotFound = ThrowIfKeyNotFound; | |
var key = indexes[0] as string; | |
result = default(TValue); | |
var hasValue = Source.TryGetValue(key, out var tres); | |
if (hasValue) | |
result = tres; | |
else if (throwIfNotFound) | |
ThrowNotFound(key); | |
return true; | |
} | |
public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value) | |
{ | |
//dynamic cln = indexes[0]; -- I think this would be slow, since it has to reflect on every call | |
var key = indexes[0] as string; | |
var throwIfNotFound = (ThrowIfKeyNotFoundSetter ?? ThrowIfKeyNotFound); | |
if (throwIfNotFound && !Source.ContainsKey(key)) | |
ThrowNotFound(key); | |
Source[key] = (TValue)value; | |
return true; | |
} | |
protected void ThrowNotFound(string key, bool assert = false) | |
{ | |
throw new KeyNotFoundException($"Key '{key}' not found in the dictionary"); | |
} | |
/// <summary> | |
/// Returns a value from a DataRow items array. | |
/// If the field doesn't exist null is returned. | |
/// DbNull values are turned into .NET nulls. | |
/// | |
/// </summary> | |
/// <param name="binder"></param> | |
/// <param name="result"></param> | |
/// <returns></returns> | |
public override bool TryGetMember(GetMemberBinder binder, out object result) | |
{ | |
result = default(TValue); | |
var throwIfNotFound = ThrowIfKeyNotFound; | |
var member = binder.Name; | |
var hasKey = KeyByMember.TryGetValue(member, out var key); | |
var tres = default(TValue); | |
var hasResult = hasKey && Source.TryGetValue(key??string.Empty, out tres); | |
if (hasResult) | |
{ | |
result = tres; | |
return true; | |
} | |
if (throwIfNotFound) | |
ThrowNotFound(key); | |
return true; | |
} | |
/// <summary> | |
/// Property setter implementation tries to retrieve value from instance | |
/// first then into this object | |
/// </summary> | |
/// <param name="binder"></param> | |
/// <param name="value"></param> | |
/// <returns></returns> | |
public override bool TrySetMember(SetMemberBinder binder, object value) | |
{ | |
var throwIfNotFound = ThrowIfKeyNotFound; | |
var name = binder.Name; | |
var hasKey = KeyByMember.TryGetValue(name, out var key); | |
if (!hasKey) key = name; | |
if (throwIfNotFound && !Source.ContainsKey(key)) | |
ThrowNotFound(key); | |
Source[key] = (TValue)value; | |
return true; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment