Last active
November 26, 2019 04:56
-
-
Save chrcar01/28319634aafd2897574c54ab331418eb to your computer and use it in GitHub Desktop.
When legacy code is untestable and concrete types have 30+ constructor parameters...
This file contains 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.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