Make a class specific generic method - c#

I have created this Update method
public void Update(Person updated)
{
var oldProperties = GetType().GetProperties();
var newProperties = updated.GetType().GetProperties();
for (var i = 0; i < oldProperties.Length; i++)
{
var oldval = oldProperties[i].GetValue(this, null);
var newval = newProperties[i].GetValue(updated, null);
if (oldval != newval)
oldProperties[i].SetValue(this, newval, null);
}
}
What it does is comparing two Person objects and if there is any new values. It updates the original object. This works great, but being a lazy programmer, I would like it to be more reusable.
I would like it to work like this.
Person p1 = new Person(){Name = "John"};
Person p2 = new Person(){Name = "Johnny"};
p1.Update(p2);
p1.Name => "Johnny"
Car c1 = new Car(){Brand = "Peugeot"};
Car c2 = new Car(){Brand = "BMW"};
c1.Update(c2);
c1.Brand => "BMW"
c1.Update(p1); //This should not be possible and result in an error.
I was thinking about using and Abstract Class to hold the Method and then use some Generic, but I don't know how to make it Class specific.

public static void Update(object original, object updated)
{
var oldProperties = original.GetType().GetProperties();
var newProperties = updated.GetType().GetProperties();
for (var i = 0; i < oldProperties.Length; i++)
{
var oldval = oldProperties[i].GetValue(original, null);
var newval = newProperties[i].GetValue(updated, null);
if (!Equals(oldval,newval))
oldProperties[i].SetValue(original, newval, null);
}
}
or if you want to ensure the same type:
public static void Update<T>(T original, T updated)
{
var properties = typeof(T).GetProperties();
for (var i = 0; i < properties.Length; i++)
{
var oldval = properties[i].GetValue(original, null);
var newval = properties[i].GetValue(updated, null);
if (!Equals(oldval,newval))
properties[i].SetValue(original, newval, null);
}
}

Your code has a little flaw, in the fact that if you don't enforce that the two objects are effectively of the exact same type, they may not have the same properties and you would face errors.
A generic method like this should operate correctly on almost anything, as long as it's a class (that's what the constraint where T: class is there for: if it's not a class you're passing, code won't compile).
static void Update<T>(T original, T updated) where T : class
{
var Properties = typeof(T).GetProperties();
foreach (PropertyInfo property in Properties)
{
var oldval = property.GetValue(original, null);
var newval = property.GetValue(updated, null);
if (oldval != newval) property.SetValue(original, newval, null);
}
}

Try this pattern:
interface IUpdateable
{
void Update(IUpdateable updated)
}
public void Update<T>(T updated) where T:IUpdateable
{
...
...
}

Related

Generic Converters for struct to class and vice versa

I want to have two converters like these:
public class PacMan<T2> where T2 : new()
{
public static List<T1> ArrayToList<T1>(T2[] array)
{
var list = new List<T1>(array.Length);
for (int i = 0; i < array.Length; i++) list.Add(array[i]);
return list;
}
public static T2[] ListToArray<T1>(List<T1> list)
{
var array = new T2[list.Count];
for (int i = 0; i < list.Count; i++) array[i] = list[i];
return array;
}
}
where T1 is a class and T2 is a struct. Both the class and struct members have Identical names and types. With the above I get red squigly in first method's list.Add(array[i]) and second methods array[i] = list[i] so these don't work. What's the easiest way to do that?
EDIT
Here's the class:
public class PerSec : INotifyPropertyChanged
{
string yq;
float eps, nav, cash, debt;
public string YQ { get => yq; set { yq = value; OnPropertyChanged(); } }
public float EPS { get => eps; set { eps = value; OnPropertyChanged(); } }
public float NAV { get => nav; set { nav = value; OnPropertyChanged(); } }
public float Cash { get => cash; set { cash = value; OnPropertyChanged(); } }
public float Debt { get => debt; set { debt = value; OnPropertyChanged(); } }
#region Notify Property Changed Members
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName] string name = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
#endregion
}
and here's the struct:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct PerSecStruct
{
//23 bytes
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 7)]
public string YQ;
public float EPS;
public float NAV;
public float Cash;
public float Debt;
}
EDIT
In second method, I've these now:
public static T2[] ListToarray<T1>(List<T1> list)
{
var structFields = typeof(PerSecStruct).GetFields(BindingFlags.Instance | BindingFlags.Public);
var classFields = typeof(PerSec).GetProperties(BindingFlags.Instance | BindingFlags.Public);
classFields = classFields.Where(x => structFields.Select(y => y.Name).Contains(x.Name)).ToArray();
var fieldsDictionary = structFields.Zip(classFields, (k, v) => new { StructField = k, ClassField = v }).ToDictionary(x => x.StructField, x => x.ClassField);
var array = new T2[list.Count];
for (int i = 0; i < list.Count; i++)
{
var psStruct = array[i];
var psClass = list[i];
foreach (var entry in fieldsDictionary)
{
var value = entry.Value.GetValue(psClass);
entry.Key.SetValue(psStruct, value);
}
}
return array;
}
this entry.Key.SetValue(psStruct, value); line isn't working so the elements of array have their default values (null/0).
EDIT
It works if I use __makeref(array[i]) as noted here by petelids. With that I can do this:
public static T2[] ListToarray<T1>(List<T1> list)
{
var fields = typeof(T2).GetFields();
var properties = typeof(T1).GetProperties();
var array = new T2[list.Count];
for (int i = 0; i < list.Count; i++)
{
foreach (var field in fields)
{
var value = properties.First(x => x.Name == field.Name).GetValue(list[i]);
field.SetValueDirect(__makeref(array[i]), value);
}
}
return array;
}
I don't need those Binding Flags! And to convert back to List I've to do this in other method:
public static List<T1> ArrayToList<T1>(T2[] array) where T1 : new()
{
var fields = typeof(T2).GetFields();
var properties = typeof(T1).GetProperties();
var list = new List<T1>(array.Length);
for (int i = 0; i < array.Length; i++)
{
var obj = new T1();
foreach (var property in properties)
{
var value = fields.First(x => x.Name == property.Name).GetValue(array[i]);
property.SetValue(obj, value);
}
list.Add(obj);
}
return list;
}
You could use most of serializers for that, for example with Json.NET:
using Newtonsoft.Json;
...
internal static class MyConverter
{
internal static T Convert<T>(object source)
{
string json = JsonConvert.SerializeObject(source);
T result = JsonConvert.DeserializeObject<T>(json);
return result;
}
}
usage:
var s1 = new PerSecStruct { YQ = "1", EPS = 2, NAV = 3, Cash = 4, Debt = 5 };
// to object
var o = MyConverter.Convert<PerSec>(s1);
// back to struct
var s2 = MyConverter.Convert<PerSecStruct>(o);
This can be done using Reflection, although I also highly advise you look at AutoMapper as Knoop pointed out in the comments, but if you only need it for this one struct you could code your own implementation.
Reflection "is the ability of a process to examine, introspect, and modify its own structure and behaviour", in C# we use the System.Reflection namespace for this.
So we want to map all public fields of one struct to all public properties of another, for this we first need to get all public fields on our struct and all the properties on our class like so:
(For this we need an instance of each)
var psStruct = ...;
var psClass =...;
// BindingFlags.Instance means all instance fields (non-static)
// BindingFlags.Public means only public fields
var structFields = typeof(PerSecStruct).GetFields(BindingFlags.Instance | BindingFlags.Public);
// The BindingFlags are the same, but we use 'GetProperties' because you declared properties not fields
var classFields = typeof(PerSec).GetProperties(BindingFlags.Instance | BindingFlags.Public);
So now we have a list of both the fields on the struct and the Properties on the class, so we can actually begin mapping:
// First filter the list on the Class to make sure we only have properties the struct has as well
classFields = classFields.Where(x => structFields.Select(y => y.Name).Contains(x.Name));
// ToDictionary to combine both lists into one
var fieldsDictionary = structFields.Zip(classFields, (k, v) => new {StructField = k, ClassField = v}).ToDictionary(x => x.StructField, x => x.ClassField);
foreach (var entry in fieldsDictionary)
{
// Get the value from the psStruct object
var value = entry.Key.GetValue(psStruct);
// Set value on the psClass object (this can be a one-liner but I prefer this as it is more readable)
entry.Value.SetValue(psClass, value);
}
A concrete example of how to use this:
public static List<TTarget> As<TSource>(IEnumerable<TSource> source) where TTarget : new();
{
var sourceFields = typeof(TSource).GetFields(BindingFlags.Instance | BindingFlags.Public);
var targetProperties = typeof(TTarget).GetProperties(BindingFlags.Instance | BindingFlags.Public);
var mapping = sourceFields.Zip(targetProperties, (k, v) => new {Source = k, Target = v}).ToDictionary(x => x.Source, x => x.Target);
var retval = new List<TTarget>();
foreach (var sourceObject in source)
{
var mappedObject = new TTarget();
foreach (var m in mapping)
{
var value = entry.Key.GetValue(sourceObject);
entry.Value.SetValue(mappedObject, value);
}
retval.Add(mappedObject);
}
return retval;
}
I added an OrderBy clause to getting the struct/ class fields/ Properties because else the order or declaration inside the struct/ class would have had to have been the same, like this it doesn't matter.
This currently does not work both ways, but if you decide to replace the fields inside your struct with Properties you can replace the typeof(TSource).GetFields(...) with typeof(TSource).GetProperties(...), then it would work both ways

Linq Select dynamically from generic <T> List

I want to get a list of each object from my List<T> (except strings, ints etc). And then Invoke (generic, recursive method with reflection). The problem is I am iterating on the property names, and have no idea how to select.
Error CS0021 Cannot apply indexing with [] to an expression of type 'T'
Code:
public static void My method<T>(IEnumerable<T> query)
{
var t = typeof(T);
var Headings = t.GetProperties();
for (int i = iteratorStart; i < Headings.Count(); i++)
{
if (IsValue(Headings[i].PropertyType.FullName))
{
}
else
{
Type type = Type.GetType(Headings[i].PropertyType.FullName);
var mi = typeof(ExcelExtension);
var met = mi.GetMethod("ListToExcel");
var genMet = met.MakeGenericMethod(type);
var nested = query.Select(p => p[Headings[i].Name]);
object[] parametersArray = new object[] { pck, nested, i };
genMet.Invoke(null, parametersArray);
}
}
}
As far as I can see, this is what you want:
public static void Mymethod<T>(IEnumerable<T> query)
{
var t = typeof(T);
int pck = 1234;
var mi = typeof(ExcelExtension);
var met = mi.GetMethod("ListToExcel");
var Headings = t.GetProperties();
for(int i=0; i < Headings.Length; ++i)
{
var prop = Headings[i];
if (prop.PropertyType.IsClass)
{
var genMet = met.MakeGenericMethod(prop.PropertyType);
var nested = query.Select(p => prop.GetValue(p));
object[] parametersArray = new object[] { pck, nested, i };
genMet.Invoke(null, parametersArray);
}
}
}
class ExcelExtension
{
public void ListToExcel<T>(int pck, IEnumerable<object> nested, int i)
{
}
}
Assuming you are using c# 6.0 or higher. You can use generic type parameters like;
public static void MyMethod<T>(IEnumerable<T> query) where T : IList
{
//Your code here
}
This way, you ensure that T is List of something and reaching indexing won't be a problem.
UPDATE
I misunderstood the question earlier. Here is the updated solution.
public static void MyMethod<T>(IEnumerable<T> query)
{
var t = typeof(T);
var Headings = t.GetProperties();
for (int i = iteratorStart; i < Headings.Count(); i++)
{
if (false == IsValue(Headings[i].PropertyType.FullName))
{
Type type = Type.GetType(Headings[i].PropertyType.FullName);
var mi = typeof(ExcelExtension);
var met = mi.GetMethod("ListToExcel");
var genMet = met.MakeGenericMethod(type);
//Assuming you want to get property value here. IF not You can use like Headings[i].GetName
var nested = query.Select(p =>Convert.ChangeType( Headings[i].GetValue(p),Headings[i].GetType()));
object[] parametersArray = new object[] { pck, nested, i };
genMet.Invoke(null, parametersArray);
}
}
}
Error Explanation:
The problem is in the Select(p => p[something here]) part. Since p is not the property list or array but a type of object, it doesn't contain any indexer. You should use reflection like above example.

C# Refactoring the same action with different details using design patterns

I try to find the way for refactoring my code but no idea how to do this.
For example, we have several classes
class A {
string name;
int year;
}
class B {
long id;
string code;
DateTime currentTime;
}
class C {
string lastname;
DateTime currentDate;
}
And latter I need to return list of objects of these classes List, List, List and convert them into Object[][].
For every conversion I do the same
private Object[][] converAListToObjectArray(List<A> dataList)
{
long countRecords = dataList.Count();
const int countProperty = 2;
var arrayRes = new object[countRecords][];
for (int i = 0; i < countRecords; i++)
{
var arrayObjProperty = new object[countProperty];
arrayObjProperty[0] = dataList[i].Name;
arrayObjProperty[1] = dataList[i].Year;
arrayRes[i] = arrayObjProperty;
}
return arrayRes;
}
private Object[][] converBListToObjectArray(List<B> dataList)
{
long countRecords = dataList.Count();
const int countProperty = 3;
var arrayRes = new object[countRecords][];
for (int i = 0; i < countRecords; i++)
{
var arrayObjProperty = new object[countProperty];
arrayObjProperty[0] = dataList[i].Id;
arrayObjProperty[1] = dataList[i].Code;
arrayObjProperty[2] = dataList[i].CurrentTime;
arrayRes[i] = arrayObjProperty;
}
return arrayRes;
}
Is it possible separate this convertion using some design pattern?
You could write a generic function which uses reflection to get each object's field names and values. You'd need to decide whether it should work for public or private fields. The example below grabs both the public and the private fields:
static object[][] ConvertToObjectArray<T>(IList<T> objects)
{
var fields = (from fieldInfo in typeof(T).GetFields(
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)
orderby fieldInfo.Name
select fieldInfo).ToArray();
object[][] table = new object[objects.Count][];
for (int i = 0; i < table.Length; i++)
{
table[i] = (from fieldInfo in fields
select fieldInfo.GetValue(objects[i])).ToArray();
}
return table;
}
You could use an action and do something like this: -
class Program
{
// Your new function, (doesn't have to be static; just did it for the demo)
// If you really really want to return object[][] still,
// You'll need to pass an index to foo as well
private static List<IList<object>> convert<T>(IList<T> dataList, Action<IList<object>, T> foo)
{
var arrayRes = new List<IList<object>>();
foreach (var item in dataList)
{
var arrayObjProperty = new List<object>();
foo(arrayObjProperty, item);
arrayRes.Add(arrayObjProperty);
}
return arrayRes;
}
// The rest is just calling the function with two examples
static void Main(string[] args)
{
var bar = new List<A>();
bar.Add(new A() { name = "qux", year = 2013 });
var objects1 = convert(bar, (a, b) =>
{
a.Add(b.name);
a.Add(b.year);
});
var baz = new List<B>();
baz.Add(new B() { code = "qux", id = 2013 });
var objects2 = convert(baz, (a, b) =>
{
a.Add(b.code);
a.Add(b.id);
});
}
}
You can just copy this into your IDE to have a play and see how it works. Basically, this uses generics and then an action to allow you to do the only part that differs each time in a lambda that is passed to the method.
You could simplify and use something like this - e.g. for B...
List<B> list = new List<B>
{
new B{ id = 1, code = "", currentTime = DateTime.Now},
new B{ id = 1, code = "", currentTime = DateTime.Now},
new B{ id = 1, code = "", currentTime = DateTime.Now},
};
var array = list.Select(x => new object[] { x.id, x.code, x.currentTime }).ToArray();
Your Object[][] structure is a little unusual to my eye. Did you know there's a Cast method for lists?
You can do the following:
List<A> foo = new List<A> ();
// initialize foo here
var bar = foo.Cast<Object>().ToArray();
Which will get you a an array of objects with your field names intact. If you absolutely need to have the fields as array elements (why do you want this) you could add a ToObject method to each of your classes:
class A
{
public string name;
public int year;
public Object[] ToObject()
{
return new Object[] {name, year};
}
}
List<A> foo = new List<A>
{
new A{name="reacher",year=2013},
new A{name="Ray",year=2013}
};
var bux = foo.Select(a => a.ToObject()).ToArray() ;

PropertyInfo.GetValue() - how do you index into a generic parameter using reflection in C#?

This (shortened) code..
for (int i = 0; i < count; i++)
{
object obj = propertyInfo.GetValue(Tcurrent, new object[] { i });
}
.. is throwing a 'TargetParameterCountException : Parameter count mismatch' exception.
The underlying type of 'propertyInfo' is a Collection of some T. 'count' is the number of items in the collection. I need to iterate through the collection and perform an operation on obj.
Advice appreciated.
Reflection only works on one level at a time.
You're trying to index into the property, that's wrong.
Instead, read the value of the property, and the object you get back, that's the object you need to index into.
Here's an example:
using System;
using System.Collections.Generic;
using System.Reflection;
namespace DemoApp
{
public class TestClass
{
public List<Int32> Values { get; private set; }
public TestClass()
{
Values = new List<Int32>();
Values.Add(10);
}
}
class Program
{
static void Main()
{
TestClass tc = new TestClass();
PropertyInfo pi1 = tc.GetType().GetProperty("Values");
Object collection = pi1.GetValue(tc, null);
// note that there's no checking here that the object really
// is a collection and thus really has the attribute
String indexerName = ((DefaultMemberAttribute)collection.GetType()
.GetCustomAttributes(typeof(DefaultMemberAttribute),
true)[0]).MemberName;
PropertyInfo pi2 = collection.GetType().GetProperty(indexerName);
Object value = pi2.GetValue(collection, new Object[] { 0 });
Console.Out.WriteLine("tc.Values[0]: " + value);
Console.In.ReadLine();
}
}
}
I was most of the way there until I saw this, and I am posting this because I didn't see it anywhere else; the key was using GetValue(collection, new Object[] { i }); in the loop rather than trying to use GetValue(collection, new Object[i]); outside the loop.
(You can probably ignore the "output" in my example);
private static string Recursive(object o)
{
string output="";
Type t = o.GetType();
if (t.GetProperty("Item") != null)
{
System.Reflection.PropertyInfo p = t.GetProperty("Item");
int count = -1;
if (t.GetProperty("Count") != null &&
t.GetProperty("Count").PropertyType == typeof(System.Int32))
{
count = (int)t.GetProperty("Count").GetValue(o, null);
}
if (count > 0)
{
object[] index = new object[count];
for (int i = 0; i < count; i++)
{
object val = p.GetValue(o, new object[] { i });
output += RecursiveWorker(val, p, t);
}
}
}
return output;
}
Assembly zip_assembly = Assembly.LoadFrom(#"C:\Ionic.Zip.Reduced.dll");
Type ZipFileType = zip_assembly.GetType("Ionic.Zip.ZipFile");
Type ZipEntryType = zip_assembly.GetType("Ionic.Zip.ZipEntry");
string local_zip_file = #"C:\zipfile.zip";
object zip_file = ZipFileType.GetMethod("Read", new Type[] { typeof(string) }).Invoke(null, new object[] { local_zip_file });
// Entries is ICollection<ZipEntry>
IEnumerable entries = (IEnumerable)ZipFileType.GetProperty("Entries").GetValue(zip_file, null);
foreach (object entry in entries)
{
string file_name = (string)ZipEntryType.GetProperty("FileName").GetValue(entry, null);
Console.WriteLine(file_name);
}

Create instance of generic type whose constructor requires a parameter?

If BaseFruit has a constructor that accepts an int weight, can I instantiate a piece of fruit in a generic method like this?
public void AddFruit<T>()where T: BaseFruit{
BaseFruit fruit = new T(weight); /*new Apple(150);*/
fruit.Enlist(fruitManager);
}
An example is added behind comments. It seems I can only do this if I give BaseFruit a parameterless constructor and then fill in everything through member variables. In my real code (not about fruit) this is rather impractical.
-Update-
So it seems it can't be solved by constraints in any way then. From the answers there are three candidate solutions:
Factory Pattern
Reflection
Activator
I tend to think reflection is the least clean one, but I can't decide between the other two.
Additionally a simpler example:
return (T)Activator.CreateInstance(typeof(T), new object[] { weight });
Note that using the new() constraint on T is only to make the compiler check for a public parameterless constructor at compile time, the actual code used to create the type is the Activator class.
You will need to ensure yourself regarding the specific constructor existing, and this kind of requirement may be a code smell (or rather something you should just try to avoid in the current version on c#).
You can't use any parameterised constructor. You can use a parameterless constructor if you have a "where T : new()" constraint.
It's a pain, but such is life :(
This is one of the things I'd like to address with "static interfaces". You'd then be able to constrain T to include static methods, operators and constructors, and then call them.
Yes; change your where to be:
where T:BaseFruit, new()
However, this only works with parameterless constructors. You'll have to have some other means of setting your property (setting the property itself or something similar).
Most simple solution
Activator.CreateInstance<T>()
As Jon pointed out this is life for constraining a non-parameterless constructor. However a different solution is to use a factory pattern. This is easily constrainable
interface IFruitFactory<T> where T : BaseFruit {
T Create(int weight);
}
public void AddFruit<T>( IFruitFactory<T> factory ) where T: BaseFruit {
BaseFruit fruit = factory.Create(weight); /*new Apple(150);*/
fruit.Enlist(fruitManager);
}
Yet another option is to use a functional approach. Pass in a factory method.
public void AddFruit<T>(Func<int,T> factoryDel) where T : BaseFruit {
BaseFruit fruit = factoryDel(weight); /* new Apple(150); */
fruit.Enlist(fruitManager);
}
You can do by using reflection:
public void AddFruit<T>()where T: BaseFruit
{
ConstructorInfo constructor = typeof(T).GetConstructor(new Type[] { typeof(int) });
if (constructor == null)
{
throw new InvalidOperationException("Type " + typeof(T).Name + " does not contain an appropriate constructor");
}
BaseFruit fruit = constructor.Invoke(new object[] { (int)150 }) as BaseFruit;
fruit.Enlist(fruitManager);
}
EDIT: Added constructor == null check.
EDIT: A faster variant using a cache:
public void AddFruit<T>()where T: BaseFruit
{
var constructor = FruitCompany<T>.constructor;
if (constructor == null)
{
throw new InvalidOperationException("Type " + typeof(T).Name + " does not contain an appropriate constructor");
}
var fruit = constructor.Invoke(new object[] { (int)150 }) as BaseFruit;
fruit.Enlist(fruitManager);
}
private static class FruitCompany<T>
{
public static readonly ConstructorInfo constructor = typeof(T).GetConstructor(new Type[] { typeof(int) });
}
As an addition to user1471935's suggestion:
To instantiate a generic class by using a constructor with one or more parameters, you can now use the Activator class.
T instance = Activator.CreateInstance(typeof(T), new object[] {...})
The list of objects are the parameters you want to supply. According to Microsoft:
CreateInstance [...] creates an instance of the specified type using the constructor that best matches the specified parameters.
There's also a generic version of CreateInstance (CreateInstance<T>()) but that one also does not allow you to supply constructor parameters.
I created this method:
public static V ConvertParentObjToChildObj<T,V> (T obj) where V : new()
{
Type typeT = typeof(T);
PropertyInfo[] propertiesT = typeT.GetProperties();
V newV = new V();
foreach (var propT in propertiesT)
{
var nomePropT = propT.Name;
var valuePropT = propT.GetValue(obj, null);
Type typeV = typeof(V);
PropertyInfo[] propertiesV = typeV.GetProperties();
foreach (var propV in propertiesV)
{
var nomePropV = propV.Name;
if(nomePropT == nomePropV)
{
propV.SetValue(newV, valuePropT);
break;
}
}
}
return newV;
}
I use that in this way:
public class A
{
public int PROP1 {get; set;}
}
public class B : A
{
public int PROP2 {get; set;}
}
Code:
A instanceA = new A();
instanceA.PROP1 = 1;
B instanceB = new B();
instanceB = ConvertParentObjToChildObj<A,B>(instanceA);
You can use the following command:
T instance = (T)typeof(T).GetConstructor(new Type[0]).Invoke(new object[0]);
Be sure to see the following
reference.
Recently I came across a very similar problem. Just wanted to share our solution with you all. I wanted to I created an instance of a Car<CarA> from a json object using which had an enum:
Dictionary<MyEnum, Type> mapper = new Dictionary<MyEnum, Type>();
mapper.Add(1, typeof(CarA));
mapper.Add(2, typeof(BarB));
public class Car<T> where T : class
{
public T Detail { get; set; }
public Car(T data)
{
Detail = data;
}
}
public class CarA
{
public int PropA { get; set; }
public CarA(){}
}
public class CarB
{
public int PropB { get; set; }
public CarB(){}
}
var jsonObj = {"Type":"1","PropA":"10"}
MyEnum t = GetTypeOfCar(jsonObj);
Type objectT = mapper[t]
Type genericType = typeof(Car<>);
Type carTypeWithGenerics = genericType.MakeGenericType(objectT);
Activator.CreateInstance(carTypeWithGenerics , new Object[] { JsonConvert.DeserializeObject(jsonObj, objectT) });
If you are willing to use a c# precompiler, you could resolve this so that it does have compile time constraints:
// Used attribute
[AttributeUsage(AttributeTargets.Parameter)]
class ResolvedAsAttribute : Attribute
{
public string Expression;
public ResolvedAsAttribute(string expression)
{
this.Expression = expression;
}
}
// Fruit manager source:
class FruitManager {
...
public void AddFruit<TFruit>([ResolvedAs("(int p) => new TFruit(p)")] Func<int,TFruit> ctor = null)where TFruit: BaseFruit{
BaseFruit fruit = ctor(weight); /*new Apple(150);*/
fruit.Enlist(fruitManager);
}
}
// Fruit user source:
#ResolveInclude ../Managers/FruitManager.cs
...
fruitManager.AddFruit<Apple>();
...
Your precompiler would then turn the Fruit user source into:
...
fruitManager.AddFruit<Apple>((int p) => new Apple(p));
...
Using Roslyn, your precompiler could look something like this (here is room for improvement):
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using System.Threading;
using System.Text.RegularExpressions;
public class CsResolveIncludeAnalyser : CSharpSyntaxWalker
{
private List<(string key, MethodDeclarationSyntax node)> methodsToResolve = new List<(string key, MethodDeclarationSyntax node)>();
public List<(string key, MethodDeclarationSyntax node)> Analyse(string source)
{
var tree = CSharpSyntaxTree.ParseText(source);
var syntaxRoot = tree.GetRoot();
Visit(tree.GetRoot());
return methodsToResolve;
}
public override void VisitMethodDeclaration(MethodDeclarationSyntax methodDeclaration)
{
base.VisitMethodDeclaration(methodDeclaration);
if (methodDeclaration.ParameterList.Parameters.Count > 0)
{
foreach (var parm in methodDeclaration.ParameterList.Parameters)
{
var parmHasResolvedAs = parm.AttributeLists.Where((el) => el.Attributes.Where((attr) => attr.Name is IdentifierNameSyntax && ((IdentifierNameSyntax)attr.Name).Identifier.Text.Contains("ResolvedAs")).Any()).Any();
if (parmHasResolvedAs)
{
var name = methodDeclaration.Identifier.ValueText;
methodsToResolve.Add((name, methodDeclaration));
return;
}
}
}
}
}
public class CsSwiftRewriter : CSharpSyntaxRewriter
{
private string currentFileName;
private bool withWin32ErrorHandling;
private Dictionary<string,MethodDeclarationSyntax> methodsToResolve = new Dictionary<string, MethodDeclarationSyntax>();
private Dictionary<string, MethodDeclarationSyntax> getMethodsToResolve(string source, string fileName)
{
Dictionary<string, MethodDeclarationSyntax> methodsToResolve = new Dictionary<string, MethodDeclarationSyntax>();
var path = Path.GetDirectoryName(fileName);
var lines = source.Split(new[] { '\r', '\n' });
var resolveIncludes = (from el in lines where el.StartsWith("#ResolveInclude") select el.Substring("#ResolveInclude".Length).Trim()).ToList();
var analyser = new CsResolveIncludeAnalyser();
foreach (var resolveInclude in resolveIncludes)
{
var src = File.ReadAllText(path + "/" + resolveInclude);
var list = analyser.Analyse(src);
foreach (var el in list)
{
methodsToResolve.Add(el.key, el.node);
}
}
return methodsToResolve;
}
public static string Convert(string source, string fileName)
{
return Convert(source, fileName, false);
}
public static string Convert(string source, string fileName, bool isWithWin32ErrorHandling)
{
var rewriter = new CsSwiftRewriter() { currentFileName = fileName, withWin32ErrorHandling = isWithWin32ErrorHandling };
rewriter.methodsToResolve = rewriter.getMethodsToResolve(source, fileName);
var resolveIncludeRegex = new Regex(#"(\#ResolveInclude)\b");
source = resolveIncludeRegex.Replace(source, "//$1");
var tree = CSharpSyntaxTree.ParseText(source);
var syntaxRoot = tree.GetRoot();
var result = rewriter.Visit(tree.GetRoot());
return "#line 1 \"" + Path.GetFileName(fileName) + "\"\r\n" + result.ToFullString();
}
internal List<string> transformGenericArguments(List<string> arguments, GenericNameSyntax gName, TypeParameterListSyntax typeParameterList)
{
var res = new List<string>();
var typeParameters = typeParameterList.ChildNodes().ToList();
foreach (var argument in arguments)
{
var arg = argument;
for (int i = 0; i < gName.TypeArgumentList.Arguments.Count; i++)
{
var key = typeParameters[i];
var replacement = gName.TypeArgumentList.Arguments[i].ToString();
var regex = new System.Text.RegularExpressions.Regex($#"\b{key}\b");
arg = regex.Replace(arg, replacement);
}
res.Add(arg);
}
return res;
}
const string prefix = "";
internal List<string> extractExtraArguments(MethodDeclarationSyntax methodDeclaration)
{
var res = new List<String>();
foreach (var parm in methodDeclaration.ParameterList.Parameters)
{
foreach (var attrList in parm.AttributeLists)
{
foreach (var attr in attrList.Attributes)
{
if (attr.Name is IdentifierNameSyntax && string.Compare(((IdentifierNameSyntax)attr.Name).Identifier.Text, "ResolvedAs") == 0)
{
var programmCode = attr.ArgumentList.Arguments.First().ToString().Trim();
var trimmedProgrammCode = (programmCode.Length >= 2 && programmCode[0] == '"' && programmCode[programmCode.Length - 1] == '"') ? programmCode.Substring(1, programmCode.Length - 2) : programmCode;
res.Add(prefix + parm.Identifier.Text + ":" + trimmedProgrammCode);
}
}
}
}
return res;
}
internal List<string> extractExtraArguments(MethodDeclarationSyntax methodDeclaration, SimpleNameSyntax name)
{
var arguments = extractExtraArguments(methodDeclaration);
if (name != null && name is GenericNameSyntax)
{
var gName = name as GenericNameSyntax;
return transformGenericArguments(arguments, gName, methodDeclaration.TypeParameterList);
}
return arguments;
}
public override SyntaxNode VisitInvocationExpression(InvocationExpressionSyntax c_expressionStatement)
{
InvocationExpressionSyntax expressionStatement = (InvocationExpressionSyntax) base.VisitInvocationExpression(c_expressionStatement);
List<string> addedArguments = null;
switch (expressionStatement.Expression)
{
case MemberAccessExpressionSyntax exp:
if (methodsToResolve.ContainsKey(exp.Name?.Identifier.ValueText))
{
addedArguments = extractExtraArguments(methodsToResolve[exp.Name.Identifier.ValueText], exp.Name);
}
break;
case GenericNameSyntax gName:
if (methodsToResolve.ContainsKey(gName.Identifier.ValueText))
{
addedArguments = extractExtraArguments(methodsToResolve[gName.Identifier.ValueText], gName);
}
break;
default:
var name = (from el in expressionStatement.ChildNodes()
where el is GenericNameSyntax
select (el as GenericNameSyntax)).FirstOrDefault();
if (name != default(GenericNameSyntax))
{
if (methodsToResolve.ContainsKey(name.Identifier.ValueText))
{
addedArguments = extractExtraArguments(methodsToResolve[name.Identifier.ValueText], name);
}
}
break;
}
if (addedArguments?.Count > 0)
{
var addedArgumentsString = string.Join(",", addedArguments);
var args = expressionStatement.ArgumentList.ToFullString();
var paras = $"({(expressionStatement.ArgumentList.Arguments.Count > 0 ? string.Join(",", args.Substring(1,args.Length - 2), addedArgumentsString) : addedArgumentsString)})" ;
var argList = SyntaxFactory.ParseArgumentList(paras);
return expressionStatement.WithArgumentList(argList);
}
return expressionStatement;
}
}
The Precompiler could be called using a T4 script, optionally regenerating the source at compile time.
It is still possible, with high performance, by doing the following:
//
public List<R> GetAllItems<R>() where R : IBaseRO, new() {
var list = new List<R>();
using ( var wl = new ReaderLock<T>( this ) ) {
foreach ( var bo in this.items ) {
T t = bo.Value.Data as T;
R r = new R();
r.Initialize( t );
list.Add( r );
}
}
return list;
}
and
//
///<summary>Base class for read-only objects</summary>
public partial interface IBaseRO {
void Initialize( IDTO dto );
void Initialize( object value );
}
The relevant classes then have to derive from this interface and initialize accordingly.
Please note, that in my case, this code is part of a surrounding class, which already has <T> as generic parameter.
R, in my case, also is a read-only class. IMO, the public availability of Initialize() functions has no negative effect on the immutability. The user of this class could put another object in, but this would not modify the underlying collection.

Categories