Skip to content

Instantly share code, notes, and snippets.

@lukevenediger
Last active April 7, 2023 06:11
Show Gist options
  • Save lukevenediger/6327599 to your computer and use it in GitHub Desktop.
Save lukevenediger/6327599 to your computer and use it in GitHub Desktop.
BetterExpando is a better Expando object because you can: 1) test for the presence of a property 2) return String.Empty for all properties that aren't found 3) control whether property names are case-sensitive or not 4) augment two BetterExpando objects together to get a union of their properties.
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace statsd.net.shared
{
public class BetterExpando : DynamicObject
{
private Dictionary<string, object> _dict;
private bool _ignoreCase;
private bool _returnEmptyStringForMissingProperties;
/// <summary>
/// Creates a BetterExpando object/
/// </summary>
/// <param name="ignoreCase">Don't be strict about property name casing.</param>
/// <param name="returnEmptyStringForMissingProperties">If true, returns String.Empty for missing properties.</param>
/// <param name="root">An ExpandoObject to consume and expose.</param>
public BetterExpando(bool ignoreCase = false,
bool returnEmptyStringForMissingProperties = false,
ExpandoObject root = null)
{
_dict = new Dictionary<string, object>();
_ignoreCase = ignoreCase;
_returnEmptyStringForMissingProperties = returnEmptyStringForMissingProperties;
if (root != null)
{
Augment(root);
}
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
UpdateDictionary(binder.Name, value);
return true;
}
public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value)
{
if (indexes[0] is string)
{
var key = indexes[0] as string;
UpdateDictionary(NormalisePropertyName(key), value);
return true;
}
else
{
return base.TrySetIndex(binder, indexes, value);
}
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
var key = NormalisePropertyName(binder.Name);
if (_dict.ContainsKey(key))
{
result = _dict[key];
return true;
}
if ( _returnEmptyStringForMissingProperties )
{
result = String.Empty;
return true;
}
return base.TryGetMember(binder, out result);
}
/// <summary>
/// Combine two instances together to get a union.
/// </summary>
/// <returns>This instance but with additional properties</returns>
/// <remarks>Existing properties are not overwritten.</remarks>
public dynamic Augment(BetterExpando obj)
{
obj._dict
.Where(pair => !_dict.ContainsKey(NormalisePropertyName(pair.Key)))
.ToList()
.ForEach(pair => UpdateDictionary(pair.Key, pair.Value));
return this;
}
public dynamic Augment(ExpandoObject obj)
{
((IDictionary<string, object>)obj)
.Where(pair => !_dict.ContainsKey(NormalisePropertyName(pair.Key)))
.ToList()
.ForEach(pair => UpdateDictionary(pair.Key, pair.Value));
return this;
}
public T ValueOrDefault<T>(string propertyName, T defaultValue)
{
propertyName = NormalisePropertyName(propertyName);
return _dict.ContainsKey(propertyName)
? (T)_dict[propertyName]
: defaultValue;
}
/// <summary>
/// Check if BetterExpando contains a property.
/// </summary>
/// <remarks>Respects the case sensitivity setting</remarks>
public bool HasProperty(string name)
{
return _dict.ContainsKey(NormalisePropertyName(name));
}
/// <summary>
/// Returns this object as comma-separated name-value pairs.
/// </summary>
public override string ToString()
{
return String.Join(", ", _dict.Select(pair => pair.Key + " = " + pair.Value ?? "(null)").ToArray());
}
private void UpdateDictionary(string name, object value)
{
var key = NormalisePropertyName(name);
if (_dict.ContainsKey(key))
{
_dict[key] = value;
}
else
{
_dict.Add(key, value);
}
}
private string NormalisePropertyName(string propertyName)
{
return _ignoreCase ? propertyName.ToLower() : propertyName;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment