Skip to content

Instantly share code, notes, and snippets.

@chrcar01
Last active November 26, 2019 04:56
Show Gist options
  • Save chrcar01/28319634aafd2897574c54ab331418eb to your computer and use it in GitHub Desktop.
Save chrcar01/28319634aafd2897574c54ab331418eb to your computer and use it in GitHub Desktop.
When legacy code is untestable and concrete types have 30+ constructor parameters...
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace LazyCreatorCon
{
/// <summary>
/// Creates an instance of a shitty class that can't be mocked,
/// can't be changed, and requires dozens of constructors.
/// This will in only the ctor values supplied
/// and will fill in defaults for the rest.
/// </summary>
/// <typeparam name="T">The shitty type we're creating.</typeparam>
public class LazyConstructor<T> where T : class
{
private readonly dynamic _ctorParams;
public LazyConstructor(dynamic ctorParams)
{
_ctorParams = ctorParams ?? throw new ArgumentNullException(nameof(ctorParams));
}
/// <summary>
/// Finds the first ctor that contains all of the values
/// passed in via <paramref name="suppliedCtorParams"/>
/// </summary>
/// <param name="type">Type we are trying to create.</param>
/// <param name="suppliedCtorParams">Dictionary containing the keys(props)
/// and values of the constructor params we want to set.</param>
/// <returns></returns>
public (ConstructorInfo, ParameterInfo[]) FindMatchingCtor(Type type,
IDictionary<string, dynamic> suppliedCtorParams)
{
ConstructorInfo[] ctors = type.GetConstructors();
ConstructorInfo targetCtor = null;
ParameterInfo[] ctorParams = null;
foreach (var ctor in ctors)
{
ctorParams = ctor.GetParameters();
if (suppliedCtorParams.All(kvp => ctorParams.Select(x => x.Name)
.Contains(kvp.Key, StringComparer.OrdinalIgnoreCase)))
{
targetCtor = ctor;
break;
}
}
return (targetCtor, ctorParams);
}
public T Create()
{
// dynamic to dictionary ripped from here: https://stackoverflow.com/a/41613648
var suppliedCtorValues = ((object)_ctorParams)
.GetType()
.GetProperties()
.ToDictionary(p => p.Name, p => p.GetValue(_ctorParams), StringComparer.OrdinalIgnoreCase); // case-INsensive dictionary
var (targetCtor, ctorParams) = FindMatchingCtor(typeof(T), suppliedCtorValues);
if (targetCtor == null)
{
throw new InvalidOperationException($"It looks like we did not find a single ctor that contains all of the values you passed in as a dynamic. Double check spelling! The search is case-INsensitive.");
}
var createTypeArgs = GetConstructorValues(ctorParams, suppliedCtorValues);
var result = Activator.CreateInstance(typeof(T), createTypeArgs);
return result as T;
}
/// <summary>
/// Figures out from supplied values, which constructor param values to fill.
/// </summary>
/// <param name="ctorParams">Constructor parameters.</param>
/// <param name="suppliedCtorParams">Key/value pairs for setting the ctor values.</param>
/// <returns>Array of values ready for Activator.CreateInstance.</returns>
public object[] GetConstructorValues(ParameterInfo[] ctorParams, IDictionary<string, dynamic> suppliedCtorParams)
{
var createTypeArgs = new object[ctorParams.Length];
for (var i = 0; i < createTypeArgs.Length; i++)
{
var ctorParamName = ctorParams[i].Name;
if (suppliedCtorParams.ContainsKey(ctorParamName))
{
createTypeArgs[i] = suppliedCtorParams[ctorParamName];
}
else
{
createTypeArgs[i] = ctorParams[i].ParameterType.IsValueType
? Activator.CreateInstance(ctorParams[i].ParameterType) //default value of a value type ripped from https://stackoverflow.com/a/1281199
: null;
}
}
return createTypeArgs;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment