Skip to content

Commit

Permalink
.NET Core dynamic type generation
Browse files Browse the repository at this point in the history
  • Loading branch information
cheungt6 committed Jun 14, 2020
1 parent c4f43ae commit e86d1b2
Show file tree
Hide file tree
Showing 13 changed files with 472 additions and 0 deletions.
9 changes: 9 additions & 0 deletions ReflectionEmitClassGeneration/App.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Application x:Class="ReflectionEmitClassGeneration.App"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="View/MainWindow.xaml">
<Application.Resources>


</Application.Resources>
</Application>
17 changes: 17 additions & 0 deletions ReflectionEmitClassGeneration/App.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;

namespace ReflectionEmitClassGeneration
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
}
}
21 changes: 21 additions & 0 deletions ReflectionEmitClassGeneration/Command/CommandBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using System.Windows.Input;

namespace ReflectionEmitClassGeneration.Command
{
public abstract class CommandBase : ICommand
{
public virtual bool CanExecute(object parameter)
{
return true;
}

public abstract void Execute(object parameter);

public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
}
35 changes: 35 additions & 0 deletions ReflectionEmitClassGeneration/Command/GenericCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System;

namespace ReflectionEmitClassGeneration.Command
{
public class GenericCommand : CommandBase
{
private readonly Action _commandAction;
private readonly Func<object, bool> _canExecute;

public GenericCommand(Action commandAction)
{
_commandAction = commandAction;
}

public GenericCommand(Action commandAction, Func<object, bool> canExecute) : this(commandAction)
{
_canExecute = canExecute;
}

public override void Execute(object parameter)
{
_commandAction.Invoke();
}

public override bool CanExecute(object obj)
{
var result = ((_canExecute == null) ||
(_canExecute != null && _canExecute(obj))) &&

base.CanExecute(obj);

return result;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">

<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
<UseWPF>true</UseWPF>
</PropertyGroup>

</Project>
25 changes: 25 additions & 0 deletions ReflectionEmitClassGeneration/ReflectionEmitClassGeneration.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29411.108
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReflectionEmitClassGeneration", "ReflectionEmitClassGeneration.csproj", "{18A83D6A-D546-46A7-9D11-93B003A4F020}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{18A83D6A-D546-46A7-9D11-93B003A4F020}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{18A83D6A-D546-46A7-9D11-93B003A4F020}.Debug|Any CPU.Build.0 = Debug|Any CPU
{18A83D6A-D546-46A7-9D11-93B003A4F020}.Release|Any CPU.ActiveCfg = Release|Any CPU
{18A83D6A-D546-46A7-9D11-93B003A4F020}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A5A37B24-EFC6-4FD1-AB09-7E3DF817341F}
EndGlobalSection
EndGlobal
162 changes: 162 additions & 0 deletions ReflectionEmitClassGeneration/Types/TypeGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;

namespace ReflectionEmitClassGeneration.Types
{
public class TypeGenerator : TypeGeneratorGeneric<object>
{
public TypeGenerator(Dictionary<string, Type> properties) : base(properties)
{
}
}

/// <summary>
/// Generate a new type that uses T as the base class
/// </summary>
/// <typeparam name="T"></typeparam>
public class TypeGeneratorGeneric<T> where T : class
{
private readonly Dictionary<string, MethodInfo> _setMethods;

public TypeGeneratorGeneric(Dictionary<string, Type> properties)
{
Properties = properties;
_setMethods = new Dictionary<string, MethodInfo>();
Initialise();
}

private void Initialise()
{
var newTypeName = Guid.NewGuid().ToString();
var assemblyName = new AssemblyName(newTypeName);
var dynamicAssembly = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
var dynamicModule = dynamicAssembly.DefineDynamicModule("Main");
var dynamicType = dynamicModule.DefineType(newTypeName,
TypeAttributes.Public |
TypeAttributes.Class |
TypeAttributes.AutoClass |
TypeAttributes.AnsiClass |
TypeAttributes.BeforeFieldInit |
TypeAttributes.AutoLayout,
typeof(T));
dynamicType.DefineDefaultConstructor(MethodAttributes.Public |
MethodAttributes.SpecialName |
MethodAttributes.RTSpecialName);

foreach (var property in Properties)
AddProperty(dynamicType, property.Key, property.Value);

GeneratedType = dynamicType.CreateType();

foreach (var property in Properties)
{
var propertyInfo = GeneratedType.GetProperty(property.Key);

var setMethod = propertyInfo.GetSetMethod();
if (setMethod == null) continue;
_setMethods.Add(property.Key, setMethod);
}
}

public Type GeneratedType { private set; get; }

public Dictionary<string, Type> Properties { get; }

private static void AddProperty(TypeBuilder typeBuilder, string propertyName, Type propertyType)
{
var fieldBuilder = typeBuilder.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);
var propertyBuilder = typeBuilder.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);

var getMethod = typeBuilder.DefineMethod("get_" + propertyName,
MethodAttributes.Public |
MethodAttributes.SpecialName |
MethodAttributes.HideBySig, propertyType, Type.EmptyTypes);
var getMethodIL = getMethod.GetILGenerator();
getMethodIL.Emit(OpCodes.Ldarg_0);
getMethodIL.Emit(OpCodes.Ldfld, fieldBuilder);
getMethodIL.Emit(OpCodes.Ret);

var setMethod = typeBuilder.DefineMethod("set_" + propertyName,
MethodAttributes.Public |
MethodAttributes.SpecialName |
MethodAttributes.HideBySig,
null, new[] { propertyType });
var setMethodIL = setMethod.GetILGenerator();
Label modifyProperty = setMethodIL.DefineLabel();
Label exitSet = setMethodIL.DefineLabel();

setMethodIL.MarkLabel(modifyProperty);
setMethodIL.Emit(OpCodes.Ldarg_0);
setMethodIL.Emit(OpCodes.Ldarg_1);
setMethodIL.Emit(OpCodes.Stfld, fieldBuilder);

setMethodIL.Emit(OpCodes.Nop);
setMethodIL.MarkLabel(exitSet);
setMethodIL.Emit(OpCodes.Ret);

propertyBuilder.SetGetMethod(getMethod);
propertyBuilder.SetSetMethod(setMethod);
}

/// <summary>
/// Create a new instance of your generated type
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="values"></param>
/// <returns></returns>
public T CreateInstance(Dictionary<string, object> values = null)
{
var instance = (T)Activator.CreateInstance(GeneratedType);

if (values != null)
SetValues(instance, values);

return instance;
}

/// <summary>
/// Update the property values on an instance of your generated type
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="instance"></param>
/// <param name="values"></param>
public void SetValues(T instance, Dictionary<string, object> values)
{
foreach (var value in values)
SetValue(instance, value.Key, value.Value);
}

/// <summary>
///
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="instance"></param>
/// <param name="propertyName"></param>
/// <param name="propertyValue"></param>
public void SetValue(T instance, string propertyName, object propertyValue)
{
if (!_setMethods.TryGetValue(propertyName, out var setter))
throw new ArgumentException($"Type does not contain settter for property {propertyName}", nameof(propertyName));
setter.Invoke(instance, new[] { propertyValue });
}

/// <summary>
/// Create a new list of your new type and populate on initialisation
/// </summary>
/// <param name="values"></param>
/// <returns></returns>
public IList CreateList(T [] values = null)
{
var listGenericType = typeof(List<>);
var list = listGenericType.MakeGenericType(GeneratedType);
var constructor = list.GetConstructor(new Type[] { });
var newList = (IList)constructor.Invoke(new object[] { });
foreach (var value in values)
newList.Add(value);
return newList;
}
}
}
18 changes: 18 additions & 0 deletions ReflectionEmitClassGeneration/Types/TypeHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace ReflectionEmitClassGeneration.Types
{
public static class TypeHelper
{
public static Dictionary<string, Type> GetPublicProperties(System.Collections.IEnumerable itemSource)
{
var type = itemSource.GetType().GetTypeInfo();
var publicProperties = type.GetProperties(BindingFlags.Public);
var propertiesDict = publicProperties.ToDictionary(k => k.Name, v => v.PropertyType);
return propertiesDict;
}
}
}
29 changes: 29 additions & 0 deletions ReflectionEmitClassGeneration/View/MainWindow.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<Window x:Class="ReflectionEmitClassGeneration.View.MainWindow"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewmodel="clr-namespace:ReflectionEmitClassGeneration.ViewModel"
mc:Ignorable="d"
Title="ReflectionEmitClassGeneration" Height="450" Width="800">
<Window.DataContext>
<viewmodel:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="35"/>
<RowDefinition Height="35"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<DataGrid Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Margin="5,5,5,5" ItemsSource="{Binding Items, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
CanUserAddRows="False" CanUserDeleteRows="False" ColumnWidth="*" GridLinesVisibility="All">
</DataGrid>

<TextBox Grid.Row="1" Grid.Column="0" Text="{Binding NewPropertyName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" ></TextBox>
<Button Grid.Row="1" Grid.Column="1" Command="{Binding GenerateItemCommand}" CommandParameter="{Binding NewPropertyName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">Add Property</Button>
</Grid>
</Window>
19 changes: 19 additions & 0 deletions ReflectionEmitClassGeneration/View/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using ReflectionEmitClassGeneration.ViewModel;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;

namespace ReflectionEmitClassGeneration.View
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();

}
}
}
12 changes: 12 additions & 0 deletions ReflectionEmitClassGeneration/ViewModel/ItemViewModelBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.ComponentModel.DataAnnotations;

namespace ReflectionEmitClassGeneration.ViewModel
{
public class ItemViewModelBase: ViewModelBase
{
public ItemViewModelBase()
{

}
}
}
Loading

0 comments on commit e86d1b2

Please sign in to comment.