Skip to content

C# dynamic interface implementation at runtime

Posted in Development, and Professional life

Some context first

How did I come to write a class allowing dynamic interface implementation in the first place? Ever had to work on a huge company project over the weekend? Because it is the weekend you pick up fixes what should be easy configuration changes. Then you think it will take you only a couple of hours then you will be off to the gym. I thought that yesterday and boy I mislead myself, much mislead indeed. Basically, I had to update a couple of big projects to remove fields that are null from the JSON response. All of that listening to stuff like the Ding Dong Song, Purple Lamborghini and Slipknot’s Psychosocial. On the first project I had to add a little line to have that working, so the second one should be the same right? I actually thought I would grab another task before leaving that improvised hackathon.

The thought journey

It was all fun and games until, surprise surprise, the second project used a custom formatter. That was to do some processing on the response objects and update some values to match our apps implementation. Fair enough. But the magical line of configuration to ignore null fields when rendering json did not work there. The obvious solution was to get rid of that custom formatter. The obvious thing to do was get rid of that formatter and figure a way to have that object value setting logic without touching the project classes. I say obvious because there were hundreds of classes there and I did not feel like changing all of them even to simply add an interface and its implementation. I had to set properties that may exist for hundred of objects. This is how I started googling, going through StackOverflow to try and figure how to achieve that.

The much lower scale Newton moment

During that thinking process I realized I could try to do something with dynamic objects instead of adding a value during the json formatting process. Interestingly enough, a few minutes later the StackOverflow ex-machina did its thing and I found that post “How to extend class with an extra property“. The answer from unsung hero Mario Stopfer brought me light on something I did not know was possible. You guessed it: Dynamic interface implementation at runtime. Not really in the form I needed but it opened a door of possibilities to me and a new perspective on the property setting issue. And I started coding, building, testing, debugging like crazy. After a few hours, I achieved what did not know was a possibility a few hours before. Dynamic interface implementation was there working and solving my issue.

Dynamic interface implementation: Epilogue

I had a nice afternoon of coding at the office, lots of laughs and problem solving that provides me with an article I really enjoyed writing and a new class for my in progress .NET utility project that should appear when mature enough on Github. However since you have been reading all of this you will have the code in a preview gist along with sample code. The only issue is that it does not work with the new .NET Core (yet?) so I will update it at a later stage when I find the time and solution. That or add another version. Without any further ado, here is what I called the TypeMixer.

using System;
namespace IamNguele.Utils.Example
{
class Program
{
static void Main(string[] args)
{
var dynamicallyTypedObject = TypeMixer<object>.ExtendWith<SomeInterface>();
dynamicallyTypedObject.Things = "Stuff";
Console.WriteLine("Here is " + dynamicallyTypedObject.Things);
Console.Read();
}
}
public interface SomeInterface
{
string Things { get; set; }
}
}
view raw Program.cs hosted with ❤ by GitHub
/**
*
* Copyright (c) 2016 Jean-Dominique Nguele
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
* and associated documentation files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute,
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
namespace IamNguele.Utils
{
public static class TypeMixer<T>
{
public static readonly BindingFlags visibilityFlags = BindingFlags.Public | BindingFlags.Instance;
public static K ExtendWith<K>(T source = default(T))
{
var assemblyName = new Guid().ToString();
var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName(assemblyName), AssemblyBuilderAccess.Run);
var module = assembly.DefineDynamicModule("Module");
var type = module.DefineType(typeof(T).Name+"_"+typeof(K).Name, TypeAttributes.Public, typeof(T));
var fieldsList = new List<string>();
type.AddInterfaceImplementation(typeof(K));
foreach (var v in typeof(K).GetProperties())
{
fieldsList.Add(v.Name);
var field = type.DefineField("_" + v.Name.ToLower(), v.PropertyType, FieldAttributes.Private);
var property = type.DefineProperty(v.Name, PropertyAttributes.None, v.PropertyType, new Type[0]);
var getter = type.DefineMethod("get_" + v.Name, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.Virtual, v.PropertyType, new Type[0]);
var setter = type.DefineMethod("set_" + v.Name, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.Virtual, null, new Type[] { v.PropertyType });
var getGenerator = getter.GetILGenerator();
var setGenerator = setter.GetILGenerator();
getGenerator.Emit(OpCodes.Ldarg_0);
getGenerator.Emit(OpCodes.Ldfld, field);
getGenerator.Emit(OpCodes.Ret);
setGenerator.Emit(OpCodes.Ldarg_0);
setGenerator.Emit(OpCodes.Ldarg_1);
setGenerator.Emit(OpCodes.Stfld, field);
setGenerator.Emit(OpCodes.Ret);
property.SetGetMethod(getter);
property.SetSetMethod(setter);
type.DefineMethodOverride(getter, v.GetGetMethod());
type.DefineMethodOverride(setter, v.GetSetMethod());
}
if (source != null)
{
foreach (var v in source.GetType().GetProperties())
{
if (fieldsList.Contains(v.Name))
{
continue;
}
fieldsList.Add(v.Name);
var field = type.DefineField("_" + v.Name.ToLower(), v.PropertyType, FieldAttributes.Private);
var property = type.DefineProperty(v.Name, PropertyAttributes.None, v.PropertyType, new Type[0]);
var getter = type.DefineMethod("get_" + v.Name, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.Virtual, v.PropertyType, new Type[0]);
var setter = type.DefineMethod("set_" + v.Name, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.Virtual, null, new Type[] { v.PropertyType });
var getGenerator = getter.GetILGenerator();
var setGenerator = setter.GetILGenerator();
getGenerator.Emit(OpCodes.Ldarg_0);
getGenerator.Emit(OpCodes.Ldfld, field);
getGenerator.Emit(OpCodes.Ret);
setGenerator.Emit(OpCodes.Ldarg_0);
setGenerator.Emit(OpCodes.Ldarg_1);
setGenerator.Emit(OpCodes.Stfld, field);
setGenerator.Emit(OpCodes.Ret);
property.SetGetMethod(getter);
property.SetSetMethod(setter);
}
}
var newObject = (K)Activator.CreateInstance(type.CreateType());
return source == null ? newObject : CopyValues(source, newObject);
}
private static K CopyValues<K>(T source, K destination)
{
foreach (PropertyInfo property in source.GetType().GetProperties(visibilityFlags))
{
var prop = destination.GetType().GetProperty(property.Name, visibilityFlags);
if (prop != null && prop.CanWrite)
prop.SetValue(destination, property.GetValue(source), null);
}
return destination;
}
}
}
view raw TypeMixer.cs hosted with ❤ by GitHub

Be First to Comment

    Leave a Reply

    This site uses Akismet to reduce spam. Learn how your comment data is processed.

    %d bloggers like this: