parse type hierarchy in assembly - c#

Consider the following types in an assembly: BusinessPartnerList, BusinessPartner, PrivateData, CompanyData, AddressList, Address
Type BusinessPartnerList
{
BusinessPartner[]
}
Type BusinessPartner
{
PrivateData
CompanyData
AddressList
}
Type PrivateData
{
System.String FirstName
System.String SurName
}
Type PrivateData
{
System.String CompanName1
System.String CompanName2
}
Type AddressList
{
Address[]
}
I want to generic parse the type hierarchy, and represent them in a tree e.g. simple nodes
BusinessPartnerList[]
BusinessPartner
PrivateData
CompanyData
AddressList[]
Address
What is the best way to do this?

Unfortunately you didn't use proper C# syntax for your sample data. So I have to make some assumptions:
Type is actually class (or struct).
The contents of the types (BusinessPartner, PrivateData, CompanyData etc.) represent the types of some public properties.
To parse the type hierarchy you can use reflection. Find all public properties of a given type and return their types. Since you only want the types you can use a HashSet which will only contain distinct types:
public static HashSet<Type> GetPropertyTypes(Type type)
{
return new HashSet<Type>(type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Select(prop => prop.PropertyType));
}
However, it seems that you don't want to get information on arrays but rather on the type of the array elements. The same goes for lists. So if a type implements IEnumerable<T> you want to get information on the type T:
private static Type GetElementType(Type type)
{
Type enumerableType = type.GetInterfaces().FirstOrDefault(IsGenericEnumerable);
if (enumerableType != null)
{
Type[] genericArguments = enumerableType.GetGenericArguments();
return genericArguments[0];
}
// return 'object' for a non-generic IEnumerable
return typeof(IEnumerable).IsAssignableFrom(type) ? typeof(object) : type;
}
private static bool IsGenericEnumerable(Type type)
{
return type.IsGenericType &&
type.GetGenericTypeDefinition() == typeof(IEnumerable<>);
}
Note that for the type System.String this will return char because string implements IEnumerable<char> (I will adress that later).
The .NET framework does not have a tree structure you can use out of the box. So you need to implement it yourself:
public class Node<T>
{
public Node(T value, IEnumerable<Node<T>> children)
{
Value = value;
Children = children.ToList();
}
public T Value
{
get;
private set;
}
public List<Node<T>> Children
{
get;
private set;
}
}
This is a very basic implementation just for demonstration purposes.
Instead of returning List<Type> the GetPropertyTypes method can now return Node<Type> and it should be renamed to CreateTypeNode:
public static Node<Type> CreateTypeNode(Type type)
{
var children = type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Select(prop => GetElementType(prop.PropertyType))
.Select(CreateTypeNode);
return new Node<Type>(type, children);
}
This method uses recursion to create the full tree for the given type.
There is still a problem: What if type A references type B and vice versa? This would end up in an infinite recursive loop. And also: if a type has already been visited there is no need to do that again.
What we need is a cache for the types that have been visited. If a type is in the cache we use the information from the cache:
private static readonly Dictionary<Type, Node<Type>> _visitedTypes = new Dictionary<Type, Node<Type>>();
public static Node<Type> CreateTypeNode(Type type)
{
Node<Type> node;
if (_visitedTypes.TryGetValue(type, out node))
{
return node;
}
// add the key to the cache to prevent infinite recursion; the value will be set later
// if this type will be found again in a recursive call CreateTypeNode returns null
// (null will be filtered out then)
_visitedTypes.Add(type, null);
var properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
var types = new HashSet<Type>(properties.Select(prop => GetElementType(prop.PropertyType)));
var children = types.Select(CreateTypeNode).Where(n => n != null);
node = new Node<Type>(type, children);
_visitedTypes[type] = node;
return node;
}
I you don't want the string type to be reported as char (because string implements IEnumerable<char>) you can just add a node for string to the cache before you call GetOrCreateTypeNode for the first time:
_visitedTypes.Add(typeof(string), new Node<Type>(typeof(string), new List<Node<Type>>()));
Then check the cache in the GetElementType method:
private static Type GetElementType(Type type)
{
if (_visitedTypes.ContainsKey(type))
{
return type;
}
...
}

Related

Type.GetMethod() for polymorphic method (both generic and non-generic)

I am currently creating a custom way of deep-copying my objects. I use a static class for this functionality.
public static class CopyServer
{
public static int CopyDeep(int original)
{
return original;
}
//not shown: same for all other value types I use (long, float,...)
public static T CopyDeep<T>(T original) where T: ICopyAble
{
if (original == null)
return default;
if (original is ICopyAutofields)
return CopyAutofields(original);
return (T)original.CopyDeep();
}
private static T CopyAutofields<T>(T original)
{
Delegate del;
if (!_copyFunctions.TryGetValue(typeof(T), out del))
{
//not shown: Building expression for parameter etc.
foreach (var fieldInfo in typeof(T).GetFields())
{
//not shown: checking options set by custom attributes
MethodInfo methodInfo = typeof(CopyServer).GetMethod("CopyDeep", new[] { fieldInfo.FieldType });
//I can't remove the second param without getting an AmbiguousMatchException
if (methodInfo == null)
{
throw new Exception($"CopyDeep not defined for type {fieldInfo.FieldType}");
}
if (methodInfo.IsGenericMethod)
methodInfo = methodInfo.MakeGenericMethod(fieldInfo.FieldType);
Expression call = Expression.Call(methodInfo, readValue);
//not shown: Assign Expression
}
//not shown: return Expression and compiling
}
return ((Func<T, T>)del)(original);
}
}
I use T CopyAutofields<T> to build functions (by building and compiling Expression Trees) so I don't have to create copy-functions for each and every class I want to copy by hand. I control the copy-behaviour with Custom Attributes (I left this part in the code above since it is not relevant for my problem).
The code works fine as long as long as only fields with types for which a non-generic function exists are used. But it is not able to retrieve my generic function T CopyDeep<T>.
Example:
//This works:
public class Manager : ICopyAble,ICopyAutofields
{
public string FirstName;
public string LastName;
}
//This doesn't
//Meaning: typeof(CopyServer).GetMethod("copyDeep", new[] { fieldInfo.FieldType });
//in T copyAutofields<T> returns null for the Manager-field and my exception gets thrown
public class Employee : ICopyAble,ICopyAutofields
{
public string FirstName;
public string LastName;
public Manager Manager;
}
//This is what I was using before I started using the ICopyAutofields.
//This approach works, but its' too much too write since my classes usually
//have way more than three fields and I occasionally forget to update
//copyDeep()-function if I add new ones.
public class Employee : ICopyAble,ICopyAutofields
{
public string FirstName;
public string LastName;
public Manager Manager;
public IModable CopyDeep()
{
var result = new Employee();
result.FirstName = CopyServer.copyDeep(FirstName);
result.LastName= CopyServer.copyDeep(LastName);
result.Manager= CopyServer.copyDeep(Manager);
return result;
}
}
Long story short: I need a way of getting a matching function for a type T if both generic and non-generic functions with the right name exist.
In .NET 4.7.1 you need to use method GetMethods and filter the results:
class MyClass
{
public T M<T>(T t) { return default(T); }
public int M(int t) { return 0; }
}
var m = typeof(MyClass).GetMethod("M", new[] { typeof(string) }); // null
var m1 = typeof(MyClass).GetMethods()
.Where(mi => mi.Name == "M" && mi.GetGenericArguments().Any())
.First(); // returns generic method
In .NET Standard 2.1 (and .NET Core since 2.1) there is another way to resolve generic type arguments - Type.MakeGenericMethodParameter, like you can see it in this answer.
Also as workaround you can move your copyAutofields<T> method to generic class like CopyAutoFieldServer<T>:
public static class CopyAutoFieldServer<T>
{
public static T copyAutofields(T original) { ... }
}

Failing to shorten similar function calls using generics

I'm having huge problems with my code responsible for loading data from a database and converting it to lists of my custom data model objects.
As there are many data tables I'm going to have many Lists and I don't really want to create and assign them manually using copy/paste and just modifying the types.
Here is what I have for now, but there are errors displayed inside the LoadAllModelListsAsProperties method about failing conversions between Type and generic type parameters.
I can't figure out how get around this incompatibility of Type and <T>. What should I do?
public class DataProvider
{
private Dictionary<Type, object> Memory { get; set; }
private static Dictionary<Type, Type> AllModelTypes { get; }
static DataProvider()
{
AllModelTypes = new Dictionary<Type, Type>()
{
{ typeof(AreaModel), typeof(Areas) },
{ typeof(GroupModel), typeof(Groups) },
/* many more.... */
{ typeof(TownModel), typeof(Towns) }
};
}
public DataProvider()
{
// fill Memory dictionary with empty list instances
foreach (Type ModelType in AllModelTypes.Keys)
{
Memory.Add(ModelType, Activator.CreateInstance(
typeof(List<>).MakeGenericType(ModelType)));
}
}
public List<TModel> GetModelList<TModel>(Type modelType)
where TModel : ModelBase
{
// get the list from memory that matches the given type
return (List<TModel>)Memory[modelType];
}
public void LoadAllModelListsAsProperties()
{
var filter = (c) => true; // just simplified as example
foreach (KeyValuePair<Type, Type> item in AllModelTypes)
{
Type modelType = item.Key;
Type linqType = item.Value;
List<ModelBase> modelList = GetModelList<modelType>(modelType);
// !!!! ^^^^^^ error that it can't implicitly convert
// List<modelType> to List<Modelbase>
modelList.Clear();
modelList.AddRange(LoadListOfModels<linqType, modelType>(filter,
modelType.ModelFactoryFromLinq);
// !!!! ^^^^^^ error that "linqType"/"modelType" are variables,
// but get used like types; and that
// Type has no definition for "ModelFactoryFromLinq
}
// normally I would have to call something like that for every list type:
//AreaModels = LoadListOfModels<Areas, AreaModel>(
// filter, AreaModel.ModelFactoryFromLinq);
//GroupModels = LoadListOfModels<Groups, GroupModel>(
// filter, GroupModel.ModelFactoryFromLinq);
//TownModels = LoadListOfModels<Towns, TownModel>(
// filter, TownModel.ModelFactoryFromLinq);
}
public List<TModel> LoadListOfModels<TLinq, TModel>(
Func<TLinq, bool> filter,
Func<TLinq, TModel> modelFactory
)
where TLinq : class, ILinqClass
where TModel : ModelBase
{
using (LinqToSqlDataContext dc = new LinqToSqlDataContext())
{
return dc.GetTable<TLinq>()
.Where(filter)
.Select(modelFactory)
.ToList();
}
}
You cannot use a type variable as type
You can use the Base class as type => GetModelList<ModelBase>(modelType);
You dont need to use type for method LoadListOfModels when you pass the correct objects to the method. The compiler will determine the generic type from the parameters.
What is ModelFactoryFromLinq? If it is a property, you just have to use reflection or you could create a method to return the correct func<..> via the given type. If it is a method try this:
foreach (KeyValuePair<Type, Type> item in AllModelTypes)
{
Type modelType = item.Key;
Type linqType = item.Value;
List<ModelBase> modelList = GetModelList<ModelBase>(modelType);
// !!!! ^^^^^^ error that it can't implicitly convert
// List<modelType> to List<Modelbase>
modelList.Clear();
var ModelFactoryFromLinq = modelType.GetMethod("ModelFactoryFromLinq");
modelList.AddRange(LoadListOfModels(filter, modelFactoryFromLinq));
// !!!! ^^^^^^ error that "linqType"/"modelType" are variables,
// but get used like types; and that
// Type has no definition for "ModelFactoryFromLinq
}

dismantling, querying and reassembling generic type

I have a method
T Get<T>(string key)
{..
}
If the caller calls me with T = IEnumerable<V> I need to do:
return GetEnum<V>(key)
Thus I need to
test if T is IEnumerable<X>
get X and shove it into the GetEnum
method
I suspect that I cant do the second one
Obviously I can write a different method but thats not my contract with the existing code base.
You can do it with a little reflection, but it won't be particularly fast:
static class TheClass
{
public static T Get<T>(string key)
{
// Adjust these as required:
const BindingFlags flags = BindingFlags.Static | BindingFlags.NonPublic;
if (typeof(T).IsGenericType && typeof(IEnumerable<>) == typeof(T).GetGenericTypeDefinition())
{
Type v = typeof(T).GetGenericArguments()[0];
var baseMethod = typeof(TheClass).GetMethod("GetEnum", flags);
var realMethod = baseMethod.MakeGenericMethod(v);
return (T)(object)realMethod.Invoke(null, new[] { key });
}
// TODO: Handle other types...
}
private static IEnumerable<T> GetEnum<T>(string key)
{
// TODO: Return an enumerable...
}
}
EDIT
If you want to check whether the required return type implements IEnumerable<>, you can use:
Type enumerable = typeof(T).GetInterface("System.Collections.Generic.IEnumerable`1");
if (enumerable != null)
{
Type v = enumerable.GetGenericArguments()[0];
var baseMethod = typeof(TheClass).GetMethod("GetEnum", flags);
var realMethod = baseMethod.MakeGenericMethod(v);
return (T)(object)realMethod.Invoke(null, new[] { key });
}
However, your GetEnum<V> method will have to return a value which can be cast to T, otherwise you'll get an invalid cast exception.
For example, if your GetEnum<V> method returns new List<T>(...), then your Get<T> method will only work if T is either List<T> or an interface implemented by List<T>. If you call Get<HashSet<int>>, it will fail.

Casting List<object> to List<T> at runtime

I 'm trying to build a DI container and I 've stumbled on to the following problem: I have a method that retrieves a list of registered instances for a given type and I want to use that to inject IEnumerable<T> properties in a given object. An example of what I am trying to achieve would be the following:
class A { public IList<IExample> Objects { get; set; } }
class B: IExample {}
class C: IExample {}
Container.Register<IExample>(new B());
Container.Register<IExample>(new C());
var obj = new A();
Container.Inject(A);
Debug.Assert(A.Objects != null && A.Objects.Count == 2);
My Retrieve method returns an IList<object>, mainly because I have no type information at that moment, so I am attempting to convert that list into a List<T> at injection time. Here is a succint form of the methods doing the work:
public virtual IList<object> Retrieve(Type type)
{
var instances = Registry[type];
foreach(var instance in instances)
Inject(type, instance); // omitted
return instances;
}
public virtual void Inject<T>(T instance)
{
var properties = typeof (T).GetProperties();
foreach (var propertyInfo in properties)
{
var propertyType = propertyInfo.PropertyType;
if (!IsIEnumerable(propertyType)) continue;
var genericType = propertyType.GetGenericArguments()[0];
propertyInfo.SetValue(instance,
GetListType(genericType, Retrieve(genericType)), null);
}
}
protected virtual object GetListType(Type type, IEnumerable<object> items)
{
return items.Select(item => Convert.ChangeType(item, type)).ToList();
}
The code returns the error: System.InvalidCastException : Object must implement IConvertible. Sadly, I don't know how to proceed from here. Perhaps I am doing this all wrong. I 've thought of using generics or injecting multiple properties by hand, but I'd really like to not have to do that.
Thanks in advance for any help or ideas.
You could create a generic list like this:
public virtual IList Retrieve(Type type)
{
// ...
listType = typeof(List<>).MakeGenericType(new Type[] { type });
IList list = (IList)Activator.CreateInstance(listType);
// ...
return list
}
this list can be casted to IList<T>, because it is one.
You could consider to use IEnumerable and Cast<T>, but then you don't have an instance of a list. I don'^t know how important it is to have one.

indexed switch statement, or equivalent? .net, C#

I want to build a method which accepts a string param, and an object which I would like to return a particular member of based on the param. So, the easiest method is to build a switch statement:
public GetMemberByName(MyObject myobj, string name)
{
switch(name){
case "PropOne": return myobj.prop1;
case "PropTwo": return myobj.prop2;
}
}
This works fine, but I may wind up with a rather large list... So I was curious if there's a way, without writing a bunch of nested if-else structures, to accomplish this in an indexed way, so that the matching field is found by index instead of falling through a switch until a match is found.
I considered using a Dictionary<string, something> to give fast access to the matching strings (as the key member) but since I'm wanting to access a member of a passed-in object, I'm not sure how this could be accomplished.
I'm specifically trying to avoid reflection etc in order to have a very fast implementation. I'll likely use code generation, so the solution doesn't need to be small/tight etc.
I originally was building a dictionary of but each object was initializing it. So I began to move this to a single method that can look up the values based on the keys- a switch statement. But since I'm no longer indexed, I'm afraid the continuous lookups calling this method would be slow.
SO: I am looking for a way to combine the performance of an indexed/hashed lookup (like the Dictionary uses) with returning particular properties of a passed-in object. I'll likely put this in a static method within each class it is used for.
Here's a quick mockup of something that could work for any class (using reflection rather than a switch statement):
public static object GetMemberByName<T>(T obj, string name)
{
PropertyInfo prop = typeof(T).GetProperty(name);
if(prop != null)
return prop.GetValue(obj, null);
throw new ArgumentException("Named property doesn't exist.");
}
Or an Extension Method version (which will still work on any object type):
public static object GetMemberByName<T>(this T obj, string name)
{
PropertyInfo prop = typeof(T).GetProperty(name);
if(prop != null)
return prop.GetValue(obj, null);
throw new ArgumentException("Named property doesn't exist.");
}
Obviously there's some additional error checking you might want to do, but this would be a start.
I also returned the type object from the methods for a reason. This allows the caller to handle casting the value however they see fit (if they need to cast at all).
Here's an easy way you can use a dictionary:
Dictionary<string, Func<MyObject, object>> propertyNameAssociations;
private void BuildPropertyNameAssociations()
{
propertyNameAssociations = new Dictionary<string, Func<MyObject, object>>();
propertyNameAssociations.Add("PropOne", x => x.prop1);
propertyNameAssociations.Add("PropTwo", x => x.prop2);
}
public object GetMemberByName(MyObject myobj, string name)
{
if (propertyNameAssociations.Contains(name))
return propertyNameAssociations[name](myobj);
else
return null;
}
There are a few options you can try.
Option 1: Have the object store the property values dynamically.
public GetMemberByName(MyObject myobj, string name)
{
return myobj.GetProperty(name);
}
public class MyObject
{
private Dictionary<string, object> m_Properties = new Dictionary<string, object>();
public object GetProperty(string name)
{
return m_Properties[name];
}
public void SetProperty(string name, object value)
{
m_Properties[name] = value;
}
public object Prop1
{
get { return GetProperty("PropOne"); }
set { SetProperty("PropOne", value); }
}
public object Prop2
{
get { return GetProperty("PropTwo"); }
set { SetProperty("PropTwo", value); }
}
}
Option 2: Use reflection.
public GetMemberByName(MyObject myobj, string name)
{
return typeof(MyObject).GetProperty(name).GetValue(obj, null);
}
Option 3: Leave it the way it is.
This is a reasonable option because switch statements on string data types will be converted to a Dictionary lookup once the number case statements reaches a certain threshold. That threshold is 7 on the C# 3.0 compiler. So the lookup will be O(1) no matter how many case statements there are. It will not scan through each one.
You can use reflection to get a property dynamically at runtime. Here is a snippet from a little relection utility i wrote. This is written as an extension method which would easily allow you to get a property from your class instance
myInstance.GetProperty<string>("Title"); // Replace 'string' with the proper return value.
The code:
public static class ReflectionExtensions
{
private const BindingFlags DefaultFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
public static T GetProperty<T>(this object instance, string propertyName)
{
PropertyInfo property = GetPropertyInfo(instance, propertyName);
if (property == null)
{
var message = string.Format("The Type, '{0}' does not implement a '{1}' property", instance.GetType().AssemblyQualifiedName, propertyName);
throw new NotImplementedException(message);
}
return (T)property.GetValue(instance, null);
}
private static PropertyInfo GetPropertyInfo(object instance, string propertyName)
{
Type type = instance.GetType();
return type.GetProperty(propertyName, DefaultFlags);
}
}
Well, assuming that the Name matches the actual name of the property (unlike your example), this would probably be best handled through reflection.
You cant do it with an index, but you could use reflection.
You may want to try using something like this.
private static readonly Dictionary<Type, Dictionary<string, PropertyInfo>> _cache = new Dictionary<Type,Dictionary<string,PropertyInfo>>();
public static T GetProperty<T>(object obj, string name)
{
if (obj == null)
{
throw new ArgumentNullException("obj");
}
else if (name == null)
{
throw new ArgumentNullException("name");
}
lock (_cache)
{
var type = obj.GetType();
var props = default(Dictionary<string, PropertyInfo>);
if (!_cache.TryGetValue(type, out props))
{
props = new Dictionary<string, PropertyInfo>();
_cache.Add(type, props);
}
var prop = default(PropertyInfo);
if (!props.TryGetValue(name, out prop))
{
prop = type.GetProperty(name);
if (prop == null)
{
throw new MissingMemberException(name);
}
props.Add(name, prop);
}
return (T)prop.GetValue(obj, null);
}
}

Categories