Skip to content

Instantly share code, notes, and snippets.

@pmunin
Created September 3, 2019 18:39
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 pmunin/5a1b43d7d4c11df6f4553403e2981a6b to your computer and use it in GitHub Desktop.
Save pmunin/5a1b43d7d4c11df6f4553403e2981a6b to your computer and use it in GitHub Desktop.
Dynamic wrapper for Dictonary
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