I'm trying to use AutoMapper to map classes like this:
class FooDTO
{
public int X { get; set; }
public EmbeddedDTO Embedded { get; set; }
public class EmbeddedDTO
{
public BarDTO Y { get; set; }
public BazDTO Z { get; set; }
}
}
To classes like this:
class Foo
{
public int X { get; set; }
public Bar Y { get; set; }
public Baz Z { get; set; }
}
(FooDTO is a HAL resource)
I know I can do it by creating the map explicitly like this:
Mapper.CreateMap<FooDTO, Foo>()
.ForMember(f => f.Y, c => c.MapFrom(f => f.Embedded.Y))
.ForMember(f => f.Z, c => c.MapFrom(f => f.Embedded.Z));
Or even with a trick like this:
Mapper.CreateMap<FooDTO, Foo>()
.AfterMap((source, dest) => Mapper.Map(source.Embedded, dest));
But the problem is that I will have many similar HAL resources to map, and I'd rather not have to configure each one separately. I actually have a generic object model that looks like this:
class HalResource
{
[JsonProperty("_links")]
public IDictionary<string, HalLink> Links { get; set; }
}
class HalResource<TEmbedded> : HalResource
{
[JsonProperty("_embedded")]
public TEmbedded Embedded { get; set; }
}
class HalLink
{
[JsonProperty("href")]
public string Href { get; set; }
}
With this model, the FooDTO class is actually declared like this
class FooDTO : HalResource<FooDTO.EmbeddedDTO>
{
public int X { get; set; }
public class EmbeddedDTO
{
public int Y { get; set; }
public int Z { get; set; }
}
}
Is there a way to configure the mapping globally for all classes that inherit HalResource<TEmbedded>, so that the properties of the DTO's Embedded property are mapped directly to the target object? I tried to do it with a custom IObjectMapper, but it proved more challenging than I expected...
If your use case is as limited as presented in the question, that is:
A one-way mapping from HalResource derived instances to straight POCOS (vs bidirectional mapping)
Mapping of properties of the same name and type
The exact embedded structure you presented here
than it may make sense to setup a specific mapping yourself that takes into account this structure. This is something I tend to do if I have a very narrowly defined need for mapping with some clear mapping conventions (instead of relying on a generic mapper such as AutoMapper). For this purpose I have some building blocks that I tend to reuse in different contexts. I whipped together a mapper that applies to the problem you described from these building blocks, as shown below:
public class Mapper
{
private const BindingFlags DestConstructorFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
private const BindingFlags DestFlags = BindingFlags.Instance | BindingFlags.Public;
private const BindingFlags SrcFlags = BindingFlags.Instance | BindingFlags.Public;
private static readonly object[] NoArgs = new object[0];
private static readonly Type GenericEmbeddedSourceType = typeof(HalResource<>);
private readonly Dictionary<Type, Func<object, object>> _oneWayMap = new Dictionary<Type, Func<object, object>>();
public void CreateMap<TDestination, TSource>()
where TDestination : class
where TSource : HalResource
{
CreateMap(typeof(TDestination), typeof(TSource));
}
public void CreateMap(Type destType, Type srcType)
{
_oneWayMap[srcType] = InternalCreateMapper(destType, srcType);
}
public object Map<TSource>(TSource toMap) where TSource : HalResource
{
var mapper = default(Func<object, object>);
if (!_oneWayMap.TryGetValue(typeof(TSource), out mapper))
throw new KeyNotFoundException(string.Format("No mapping for {0} is defined.", typeof(TSource)));
return mapper(toMap);
}
public TDestination Map<TDestination, TSource>(TSource toMap)
where TDestination : class
where TSource : HalResource
{
var converted = Map(toMap);
if (converted != null && !typeof(TDestination).IsAssignableFrom(converted.GetType()))
throw new InvalidOperationException(string.Format("No mapping from type {0} to type {1} has been configured.", typeof(TSource), typeof(TDestination)));
return (TDestination)converted;
}
public void Clear()
{
_oneWayMap.Clear();
}
private static Func<object, object> InternalCreateMapper(Type destType, Type srcType)
{
// Destination specific constructor + setter map.
var destConstructor = BuildConstructor(destType.GetConstructor(DestConstructorFlags, null, Type.EmptyTypes, null));
var destSetters = destType
.GetProperties(DestFlags)
.Where(p => p.CanWrite)
.ToDictionary(k => k.Name, v => Tuple.Create(v.PropertyType, BuildSetter(v)));
// Source specific getter maps
var srcPrimPropGetters = CreateGetters(srcType);
var srcEmbeddedGetter = default(Func<object, object>);
var srcEmbeddedPropGetters = default(IDictionary<string, Tuple<Type, Func<object, object>>>);
var baseType = srcType.BaseType;
while (baseType != null && baseType != typeof(object))
{
if (baseType.IsGenericType && GenericEmbeddedSourceType.IsAssignableFrom(baseType.GetGenericTypeDefinition()))
{
var genericParamType = baseType.GetGenericArguments()[0];
if (srcPrimPropGetters.Any(g => g.Value.Item1.Equals(genericParamType)))
{
var entry = srcPrimPropGetters.First(g => g.Value.Item1.Equals(genericParamType));
srcPrimPropGetters.Remove(entry.Key);
srcEmbeddedGetter = entry.Value.Item2;
srcEmbeddedPropGetters = CreateGetters(entry.Value.Item1);
break;
}
}
baseType = baseType.BaseType;
}
// Build mapper delegate function.
return (src) =>
{
var result = destConstructor(NoArgs);
var srcEmbedded = srcEmbeddedGetter != null ? srcEmbeddedGetter(src) : null;
foreach (var setter in destSetters)
{
var getter = default(Tuple<Type, Func<object, object>>);
if (srcPrimPropGetters.TryGetValue(setter.Key, out getter) && setter.Value.Item1.IsAssignableFrom(getter.Item1))
setter.Value.Item2(result, getter.Item2(src));
else if (srcEmbeddedPropGetters.TryGetValue(setter.Key, out getter) && setter.Value.Item1.IsAssignableFrom(getter.Item1))
setter.Value.Item2(result, getter.Item2(srcEmbedded));
}
return result;
};
}
private static IDictionary<string, Tuple<Type, Func<object, object>>> CreateGetters(Type srcType)
{
return srcType
.GetProperties(SrcFlags)
.Where(p => p.CanRead)
.ToDictionary(k => k.Name, v => Tuple.Create(v.PropertyType, BuildGetter(v)));
}
private static Func<object[], object> BuildConstructor(ConstructorInfo constructorInfo)
{
var param = Expression.Parameter(typeof(object[]), "args");
var argsExp = constructorInfo.GetParameters()
.Select((p, i) => Expression.Convert(Expression.ArrayIndex(param, Expression.Constant(i)), p.ParameterType))
.ToArray();
return Expression.Lambda<Func<object[], object>>(Expression.New(constructorInfo, argsExp), param).Compile();
}
private static Func<object, object> BuildGetter(PropertyInfo propertyInfo)
{
var instance = Expression.Parameter(typeof(object), "instance");
var instanceCast = propertyInfo.DeclaringType.IsValueType
? Expression.Convert(instance, propertyInfo.DeclaringType)
: Expression.TypeAs(instance, propertyInfo.DeclaringType);
var propertyCast = Expression.TypeAs(Expression.Property(instanceCast, propertyInfo), typeof(object));
return Expression.Lambda<Func<object, object>>(propertyCast, instance).Compile();
}
private static Action<object, object> BuildSetter(PropertyInfo propertyInfo)
{
var setMethodInfo = propertyInfo.GetSetMethod(true);
var instance = Expression.Parameter(typeof(object), "instance");
var value = Expression.Parameter(typeof(object), "value");
var instanceCast = propertyInfo.DeclaringType.IsValueType
? Expression.Convert(instance, propertyInfo.DeclaringType)
: Expression.TypeAs(instance, propertyInfo.DeclaringType);
var call = Expression.Call(instanceCast, setMethodInfo, Expression.Convert(value, propertyInfo.PropertyType));
return Expression.Lambda<Action<object, object>>(call, instance, value).Compile();
}
}
Some optimizations can be performed, but performance is likely sufficient for most problems. This can then be used like:
public abstract class HalResource
{
public IDictionary<string, HalLink> Links { get; set; }
}
public abstract class HalResource<TEmbedded> : HalResource
{
public TEmbedded Embedded { get; set; }
}
public class HalLink
{
public string Href { get; set; }
}
public class FooDTO : HalResource<FooDTO.EmbeddedDTO>
{
public int X { get; set; }
public class EmbeddedDTO
{
public int Y { get; set; }
public int Z { get; set; }
}
}
public class MyMappedFoo
{
public int X { get; set; }
public int Y { get; set; }
public int Z { get; set; }
}
class Program
{
public static void Main(params string[] args)
{
// Configure mapper manually
var mapper = new Mapper();
mapper.CreateMap<MyMappedFoo, FooDTO>();
var myDTO = new FooDTO
{
X = 10,
Embedded = new FooDTO.EmbeddedDTO { Y = 5, Z = 9 }
};
var mappedFoo = mapper.Map<MyMappedFoo, FooDTO>(myDTO);
Console.WriteLine("X = {0}, Y = {1}, Z = {2}", mappedFoo.X, mappedFoo.Y, mappedFoo.Z);
Console.WriteLine("Done");
Console.ReadLine();
}
}
If your source and destination types can be discovered by convention, you can go a step further and have a builder that encodes these conventions populate the map as in the example below (again not the most optimal implementation, but there to illustrate the point):
public static class ByConventionMapBuilder
{
public static Func<IEnumerable<Type>> DestinationTypesProvider = DefaultDestTypesProvider;
public static Func<IEnumerable<Type>> SourceTypesProvider = DefaultSourceTypesProvider;
public static Func<Type, Type, bool> TypeMatcher = DefaultTypeMatcher;
public static Mapper Build()
{
var mapper = new Mapper();
var sourceTypes = SourceTypesProvider().ToList();
var destTypes = DestinationTypesProvider();
foreach (var destCandidateType in destTypes)
{
var match = sourceTypes.FirstOrDefault(t => TypeMatcher(t, destCandidateType));
if (match != null)
{
mapper.CreateMap(destCandidateType, match);
sourceTypes.Remove(match);
}
}
return mapper;
}
public static IEnumerable<Type> TypesFromAssembliesWhere(Func<IEnumerable<Assembly>> assembliesProvider, Predicate<Type> matches)
{
foreach (var a in assembliesProvider())
{
foreach (var t in a.GetTypes())
{
if (matches(t))
yield return t;
}
}
}
private static IEnumerable<Type> DefaultDestTypesProvider()
{
return TypesFromAssembliesWhere(
() => new[] { Assembly.GetExecutingAssembly() },
t => t.IsClass && !t.IsAbstract && !t.Name.EndsWith("DTO"));
}
private static IEnumerable<Type> DefaultSourceTypesProvider()
{
return TypesFromAssembliesWhere(
() => new[] { Assembly.GetExecutingAssembly() },
t => typeof(HalResource).IsAssignableFrom(t) && !t.IsAbstract && t.Name.EndsWith("DTO"));
}
private static bool DefaultTypeMatcher(Type srcType, Type destType)
{
var stn = srcType.Name;
return (stn.Length > 3 && stn.EndsWith("DTO") && destType.Name.EndsWith(stn.Substring(0, stn.Length - 3)));
}
}
class Program
{
public static void Main(params string[] args)
{
// Configure mapper by type scanning & convention matching
var mapper = ByConventionMapBuilder.Build();
var myDTO = new FooDTO
{
X = 10,
Embedded = new FooDTO.EmbeddedDTO { Y = 5, Z = 9 }
};
var mappedFoo = mapper.Map<MyMappedFoo, FooDTO>(myDTO);
Console.WriteLine("X = {0}, Y = {1}, Z = {2}", mappedFoo.X, mappedFoo.Y, mappedFoo.Z);
Console.WriteLine("Done");
Console.ReadLine();
}
}
If you have other reasons to want to hang on to AutoMapper, I suggest creating a similar map builder that encodes both the type matching and the embedded property mapping.
Related
I want a entity that filters the list entity within the entity.
I have entities with common structure like below
public class FoodMenuT
{
public int Id {get;set;}
public int LanguageId {get;set;}
public string Name {get;set;}
}
public class FoodMenu
{
public int Id {get;set;}
public IEnumerable<FoodMenuT> FoodMenuTList {get;set;}
}
public static Expression<Func<FoodMenu, IEnumerable<FoodMenuT>>> LanguageFilter()
{
return e => e.FoodMenuT.Where(x => x.LanguageId == 1);
}
What I want to do is to do this in a generic way.
public static Expression<Func<T, IEnumerable<TT>>> LanguageFilter<T, TT>()
{
}
Thank you in advance for your helps.
public static Expression<Func<T, IEnumerable<TT>>> LanguageFilter<T, TT>()
{
var headerInfo = ServiceTool.ServiceProvider.GetService<IHeaderInfo>();
var entity = typeof(T);
var ttEntity = typeof(TT);
var propertyInfo = entity.GetProperty(ttEntity.Name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase);
var arg = Expression.Parameter(entity, "e");
var property = Expression.Property(arg, propertyInfo!.Name);
//e=>e.FoodMenuT
var selector = Expression.Lambda(property, new ParameterExpression[] { arg });
var propertyInfo2 = ttEntity.GetProperty("LanguageId", BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase);
var arg2 = Expression.Parameter(ttEntity, "x");
var property2 = Expression.Property(arg2, propertyInfo2!.Name);
var languageId = Expression.Constant(headerInfo!.LanguageId);
var equalLanguage = Expression.Equal(property2, languageId);
//x=>x.LanguageId==1
var selector2 = Expression.Lambda<Func<TT, bool>>(equalLanguage, arg2);
I couldn't combine "selector" and "selector2" with "where".
var lambda = Expression.Lambda<Func<T, IEnumerable<TT>>>(null, arg);
return lambda;
}
I solved the problem with my own means
public interface ITEntity<TT>
{
public List<TT> Translate { get; set; }
}
public interface ILEntity
{
public int LanguageId { get; set; }
}
public class FoodMenu : ITEntity<FoodMenuT>
{
public virtual List<FoodMenuT> Translate { get; set; } = new();
}
public class FoodMenuT : ILEntity
{
public int LanguageId { get; set; }
}
public static Expression<Func<T, IEnumerable<TT>>> LanguageFilter<T, TT>() where T : ITEntity<TT> where TT : ILEntity
{
return e => e.Translate.Where(x => x.LanguageId == 1);
}
I am trying to write this method:
public static T Nullify<T>(T item, params Func<T, object> [] properties)
{
// Sets any specified properties to null, returns the object.
}
I will call it like this:
var kitten = new Kitten() { Name = "Mr Fluffykins", FurColour = "Brown" };
var anonymousKitten = Nullify(kitten, c => c.Name);
However I am unsure of how to do this. Any ideas?
Another approach is to do this (it doesn't need to be an extension method)
public static T Nullify<T>(this T item, params Expression<Func<T, object>> [] properties)
{
foreach(var property in properties)
{
var memberSelectorExpression = property.Body as MemberExpression;
if (memberSelectorExpression != null)
{
var propertyInfo = memberSelectorExpression.Member as PropertyInfo;
if (propertyInfo != null)
{
propertyInfo.SetValue(item, null, null);
}
}
}
return item;
}
Usage
item.Nullify(i => i.PropertyName, i => i.PropertyName2)
You'd need to pass a "setter method" not a "reader method" in properties.
static void Nullify<T, D>(T item, params Action<T, D>[] properties)
where D : class
{
foreach (var property in properties)
{
property(item, null);
}
}
usage:
Nullify<Kitten, string>(kitten, (c, d) => { c.Name = d; });
But this will just set the data for you. If you want a copy and then apply the properties, the items would probably have to be clonable (alternatively you can go though some hell with reflection):
static T Nullify<T, D>(T item, params Action<T, D>[] properties)
where D : class
where T : ICloneable
{
T copy = (T)item.Clone();
foreach (var property in properties)
{
property(copy, null);
}
return copy;
}
class Kitten : ICloneable
{
public string Name { get; set; }
public string FurColour { get; set; }
public object Clone()
{
return new Kitten() { Name = this.Name, FurColour = this.FurColour };
}
}
usage
var anonymousKitten = Nullify(kitten, (c, d) => { c.Name = d; });
Without modifying your method definition much:
namespace ConsoleApplication
{
public class Kitten : ISimpleClone<Kitten>
{
public string Name { get; set; }
public string FurColour { get; set; }
public int? Number { get; set; }
public Kitten SimpleClone()
{
return new Kitten { Name = this.Name, FurColour = this.FurColour, Number = this.Number };
}
}
public interface ISimpleClone<T>
{
T SimpleClone();
}
public class Program
{
public static PropertyInfo GetProperty<TObject, TProperty>(Expression<Func<TObject, TProperty>> propertyExpression)
{
MemberExpression body = propertyExpression.Body as MemberExpression;
if (body == null)
{
var unaryExp = propertyExpression.Body as UnaryExpression;
if (unaryExp != null)
{
body = ((UnaryExpression)unaryExp).Operand as MemberExpression;
}
}
return body.Member as PropertyInfo;
}
public static T Nullify<T>(T item, params Expression<Func<T, object>>[] properties)
where T : ISimpleClone<T>
{
// Creates a new instance
var newInstance = item.SimpleClone();
// Gets the properties that will be null
var propToNull = properties.Select(z => GetProperty<T, object>(z));
var filteredProp = propToNull
.Where(z => !z.PropertyType.IsValueType || Nullable.GetUnderlyingType(z.PropertyType) != null) // Can be null
.Where(z => z.GetSetMethod(false) != null && z.CanWrite); // Can be set
foreach (var prop in filteredProp)
{
prop.SetValue(newInstance, null);
}
return newInstance;
}
public static void Main(string[] args)
{
var kitten = new Kitten() { Name = "Mr Fluffykins", FurColour = "Brown", Number = 12 };
var anonymousKitten = Nullify(kitten, c => c.Name, c => c.Number);
Console.Read();
}
}
}
Looks a bit hacky though....
I want to get back the default values of all properties in a range of objects. The logic used is that if all of the property values in the range are the same, then use that for the default value, otherwise leave it null/type default.
I'm not sure if there is a better way to do this, but I'm open to all suggestions. I have created a working solution that is fairly generic, but I want it to be more so if possible. The current problem is that I have to have the if/elseif chain of the same code with a single difference of explicitly defining the type. I couldn't figure out how to get back the GetValue of the PropertyInfo and have the type properly pass into the generic functions. Once I got the object back, it would always pass down into the Generic as 'object' instead of 'int','decimal', etc. I also ran into the boxing/unboxing issue with nullables. I tried setting up the GetPropertyValue function with a generic return, but that requires you to pass in the type, which I'm not doing since I get it inside the function.
All of this code is just a working example. My classes have hundreds of properties and with 30 different classes, thats around 3000 properties I don't want to explicitly write out.
public class MainFunction
{
public MainFunction()
{
ParentClass defaultClass = new ParentClass();
List<ParentClass> results = MyDatabaseCallThatGetsBackListOfClass();
defaultClass = Generic.GetDefaultProperty(defaultClass, results);
}
private List<ParentClass> MyDatabaseCallThatGetsBackListOfClass()
{
List<ParentClass> populateResults = new List<ParentClass>();
for (int i = 0; i < 50; i++)
{
populateResults.Add(new ParentClass()
{
Class1 = new SubClass1()
{
Property1 = "Testing",
Property2 = DateTime.Now.Date,
Property3 = true,
Property4 = (decimal?)1.14,
Property5 = (i == 1 ? 5 : 25), // different, so should return null
Class1 = new SubSubClass1()
{
Property1 = "Test"
},
Class2 = new SubSubClass2()
},
Class2 = new SubClass2()
{
Property1 = null,
Property2 = 10,
Property3 = (i == 1 ? 15 : 30), // different, so should return null
Property4 = 20
}
});
}
return populateResults;
}
}
public class ParentClass
{
public ParentClass()
{
this.Class1 = new SubClass1();
this.Class2 = new SubClass2();
}
public SubClass1 Class1 { get; set; }
public SubClass2 Class2 { get; set; }
}
public class SubClass1
{
public SubClass1()
{
this.Class1 = new SubSubClass1();
this.Class2 = new SubSubClass2();
}
public string Property1 { get; set; }
public DateTime? Property2 { get; set; }
public bool? Property3 { get; set; }
public decimal? Property4 { get; set; }
public int? Property5 { get; set; }
public bool Property6 { get; set; }
public decimal Property7 { get; set; }
public DateTime Property8 { get; set; }
public int Property9 { get; set; }
public SubSubClass1 Class1 { get; set; }
public SubSubClass2 Class2 { get; set; }
}
public class SubClass2
{
public int? Property1 { get; set; }
public int? Property2 { get; set; }
public int Property3 { get; set; }
public int Property4 { get; set; }
}
public class SubSubClass1
{
public string Property1 { get; set; }
public string Property2 { get; set; }
}
public class SubSubClass2
{
public decimal? Property1 { get; set; }
public decimal Property2 { get; set; }
}
public static class Generic
{
public static T GetDefaultProperty<T>(T defaultItem, List<T> itemList)
where T : class
{
Type defaultType = defaultItem.GetType();
var props = defaultType.GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(p => p.CanRead);
foreach (var p in props)
{
if (p.PropertyType.IsClass && p.PropertyType != typeof(string))
{
dynamic classProperty = GetPropertyValue(defaultItem, p.Name);
var classList = GetClassSubList(itemList, classProperty, p.Name);
p.SetValue(defaultItem, GetDefaultProperty(classProperty, classList), null);
}
else
{
if (p.PropertyType == typeof(int?))
{
List<int?> subList = GetPropertySubList(itemList, TypeDefault<int?>(), p.Name);
if (subList.Distinct().ToList().Count == 1)
{
p.SetValue(defaultItem, subList.FirstOrDefault(), null);
}
}
else if (p.PropertyType == typeof(bool?))
{
List<bool?> subList = GetPropertySubList(itemList, TypeDefault<bool?>(), p.Name);
if (subList.Distinct().ToList().Count == 1)
{
p.SetValue(defaultItem, subList.FirstOrDefault(), null);
}
}
else if (p.PropertyType == typeof(decimal?))
{
List<decimal?> subList = GetPropertySubList(itemList, TypeDefault<decimal?>(), p.Name);
if (subList.Distinct().ToList().Count == 1)
{
p.SetValue(defaultItem, subList.FirstOrDefault(), null);
}
}
else if (p.PropertyType == typeof(DateTime?))
{
List<DateTime?> subList = GetPropertySubList(itemList, TypeDefault<DateTime?>(), p.Name);
if (subList.Distinct().ToList().Count == 1)
{
p.SetValue(defaultItem, subList.FirstOrDefault(), null);
}
}
else if (p.PropertyType == typeof(string))
{
List<string> subList = GetPropertySubList(itemList, TypeDefault<string>(), p.Name);
if (subList.Distinct().ToList().Count == 1)
{
p.SetValue(defaultItem, subList.FirstOrDefault(), null);
}
}
else if (p.PropertyType == typeof(int))
{
List<int> subList = GetPropertySubList(itemList, TypeDefault<int>(), p.Name);
if (subList.Distinct().ToList().Count == 1)
{
p.SetValue(defaultItem, subList.FirstOrDefault(), null);
}
}
else if (p.PropertyType == typeof(bool))
{
List<bool> subList = GetPropertySubList(itemList, TypeDefault<bool>(), p.Name);
if (subList.Distinct().ToList().Count == 1)
{
p.SetValue(defaultItem, subList.FirstOrDefault(), null);
}
}
else if (p.PropertyType == typeof(decimal))
{
List<decimal> subList = GetPropertySubList(itemList, TypeDefault<decimal>(), p.Name);
if (subList.Distinct().ToList().Count == 1)
{
p.SetValue(defaultItem, subList.FirstOrDefault(), null);
}
}
else if (p.PropertyType == typeof(DateTime))
{
List<DateTime> subList = GetPropertySubList(itemList, TypeDefault<DateTime>(), p.Name);
if (subList.Distinct().ToList().Count == 1)
{
p.SetValue(defaultItem, subList.FirstOrDefault(), null);
}
}
}
}
return defaultItem;
}
private static object GetPropertyValue<T>(T item, string propertyName)
{
if (item == null || string.IsNullOrEmpty(propertyName))
{
return null;
}
PropertyInfo pi = item.GetType().GetProperty(propertyName);
if (pi == null)
{
return null;
}
if (!pi.CanRead)
{
return null;
}
return pi.GetValue(item, null);
}
private static List<TReturn> GetClassSubList<T, TReturn>(List<T> list, TReturn returnType, string propertyName)
where T : class
where TReturn : class
{
return list.Select(GetClassSelection<T, TReturn>(propertyName)).ToList();
}
private static Func<T, TReturn> GetClassSelection<T, TReturn>(string fieldName)
where T : class
where TReturn : class
{
ParameterExpression p = Expression.Parameter(typeof(T), "t");
var body = Expression.Property(p, fieldName);
return Expression.Lambda<Func<T, TReturn>>(body, new ParameterExpression[] { p }).Compile();
}
private static List<TReturn> GetPropertySubList<T, TReturn>(List<T> list, TReturn returnType, string propertyName)
where T : class
{
return list.Select(GetPropertySelection<T, TReturn>(propertyName)).ToList();
}
private static Func<T, TReturn> GetPropertySelection<T, TReturn>(string fieldName)
where T : class
{
ParameterExpression p = Expression.Parameter(typeof(T), "t");
var body = Expression.Property(p, fieldName);
return Expression.Lambda<Func<T, TReturn>>(body, new ParameterExpression[] { p }).Compile();
}
private static T TypeDefault<T>()
{
return default(T);
}
You can switch the huge IF statement block with this:
var result = itemList.Select(x => p.GetValue(x, null)).Distinct();
if (result.Count() == 1)
{
p.SetValue(defaultItem, result.First(), null);
}
If you use Distinct(), references/value types are compared using object.Equals that first tests reference equality, and later actual values. This method has only one draw-back: boxing/unboxing. Use that code for reference types.
Note: there's already a lot of boxing happening in you code.
Reflection is based on a "object", so it's pretty hard not to have boxing issues.
For example:
Type defaultType = defaultItem.GetType(); // boxing on value types.
p.SetValue(defaultItem, subList.FirstOrDefault(), null); // boxing
Boxing is minor cost with reflection. You can run benchmarks to check.
As for your actual problem; you have list of objects and you want to compare them all, recursively. If there is no difference between two objects, you want to set the property in defaultItem to a property value that all objects share.
Ignoring the reason for whatever reason you're doing this(since I don't care; rather the solution to this problem is interesting), let's continue :P
Your best bet is to generate strongly-typed comparer on startup using reflection. Generate the code using StringBuilder and later use CSharpCodeProvider() to compile from StringBuilder and return strongly-typed delegate that has no reflection overhead. This is the fastest one I can think of, right now. The only hit it will take is the first interrogation of reflection metadata on startup. It's only per T.
On production code, you can cache that strongly typed comparer onto DLL, so the hit will be only one-time event.
private static class StrongClassComparer<T>
{
public static Func<T, string> GenerateMethod()
{
var code = new StringBuilder();
// generate your STRONGLY-TYPED code here.
// into code variable.
code.Append("public class YourClassInCode { "+
" public static string YourClassStaticMethod("+ typeof(T).Name+ " test)" +
" { return string.Empty; } }");
var compiler = new CSharpCodeProvider();
var parameters = new CompilerParameters();
parameters.ReferencedAssemblies.Add(typeof (T).Assembly.Location);
parameters.CompilerOptions = "/optimize + ";
var results = compiler.CompileAssemblyFromSource(parameters, code.ToString());
var #class = results.CompiledAssembly.GetType("YourClassInCode");
var method = #class.GetMethod("YourClassStaticMethod");
return (Func<T, string>) Delegate.CreateDelegate(typeof (Func<T, string>), method);
}
}
Lets say I have this:
public class Languages
{
public string Language;
public string SpokenAbility;
public string WrittenAbility;
}
Is there a way I can load this into a dropdown so that the dropdown displays the items: Language, SpokenAbility, and WrittenAbility?
// using System.Reflection;
// using System.Linq;
IEnumerable<String> properties = typeof(Languages)
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Select(x => x.Name);
You can use reflection to get the properties, and LINQ to make it easier.
As Spontifixus pointed out you're using fields. all that needs to be switch is .GetProperties to .GetFields:
IEnumerable<String> fields = typeof(Languages)
.GetFields(BindingFlags.Public | BindingFlags.Instance)
.Select(x => x.Name);
Extension method to make it easier:
public static class FieldAndPropertyExtensions
{
/*
* Field Methods
*/
public static IEnumerable<String> GetFields<T>(this T obj, Boolean includeInheritedFields = true) where T : class
{
return getFieldsFor<T>(includeInheritedFields).Select(x => x.Name);
}
public static IEnumerable<String> GetFieldsFor<T>(Boolean includeInheritedFields = true) where T : class
{
return getFieldsFor<T>(includeInheritedFields).Select(x => x.Name);
}
public static IDictionary<String, Object> GetFieldValueDictionary<T>(this T obj, Boolean includeInheritedFields = true) where T : class
{
IEnumerable<FieldInfo> fields = getFieldsFor<T>(includeInheritedFields);
IDictionary<String, Object> result = new Dictionary<String, Object>();
foreach (var field in fields)
{
result.Add(field.Name, field.GetValue(obj));
}
return result;
}
/*
* Property Methods
*/
public static IEnumerable<String> GetProperties<T>(this T obj, Boolean includeInheritedProperties = true) where T : class
{
return getPropertiesFor<T>(includeInheritedProperties).Select(x => x.Name);
}
public static IEnumerable<String> GetPropertiesFor<T>(Boolean includeInheritedProperties = true) where T : class
{
return getPropertiesFor<T>(includeInheritedProperties).Select(x => x.Name);
}
public static IDictionary<String, Object> GetPropertyValueDictionary<T>(this T obj, Boolean includeInheritedProperties = true) where T : class
{
IEnumerable<PropertyInfo> properties = getPropertiesFor<T>(includeInheritedProperties);
IDictionary<String, Object> result = new Dictionary<String, Object>();
foreach (var property in properties)
{
result.Add(property.Name, property.GetValue(obj));
}
return result;
}
/*
* Helper methods
*/
private static IEnumerable<FieldInfo> getFieldsFor<T>(Boolean includeInheritedFields = true) where T : class
{
return typeof(T)
.GetFields(BindingFlags.Public | BindingFlags.Instance)
.Where(x => includeInheritedFields || x.DeclaringType == typeof(T));
}
private static IEnumerable<PropertyInfo> getPropertiesFor<T>(Boolean includeInheritedFields = true) where T : class
{
return typeof(T)
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(x => includeInheritedFields || x.DeclaringType == typeof(T));
}
}
Example usage:
// instance methods:
var languages = new Languages();
var properties = languages.GetProperties(); // prop1,prop2,prop3
var fields = languages.GetFields(); // field1,field2,field3
var propAndValue = languages.GetPropertyValueDictionary(); // Dict<propertyName,value>
var fieldAndValue = languages.GetFieldValueDictionary(); // Dict<fieldName,value>
// non-instance methods:
var properties = ObjectExtensions.GetPropertiesFor<Languages>(); // prop1,prop2,prop3
var fields = ObjectExtensions.GetFieldsFor<Languages>(); // field1,field2,field3
First of all you will need to make sure that your class has properties. The one defined in your question are fields. To transform them into properties simply add the get; and set;-methods:
public class Languages
{
public string Language { get; set; }
public string SpokenAbility { get; set; }
public string WrittenAbility {get; set; }
}
Then you can list the properties using the following code:
var properties = typeof(Languages).GetProperties().Select(p => p.Name)
To retrieve the value of a property use the following code:
var language = new Languages(){ Language="German" };
var result = typeof(Languages).GetProperty("Language").GetValue(language);
I have an interface that defines a method for returning an IList<PropertyInfo> :
public interface IWriteable
{
IList<PropertyInfo> WriteableProperties();
}
.
.
It is implemented in various (dissimilar) classes in the following manner:
public abstract class Foo
{
private IList<PropertyInfo> _props;
protected Foo()
{
this._props = new List<PropertyInfo>();
foreach (PropertyInfo p in this.GetType().GetProperties())
{
if (Attribute.IsDefined(p, typeof(WriteableAttribute)))
this._props.Add(p);
}
}
#region IWriteable Members
public IList<PropertyInfo> WriteableProperties()
{
return this._props;
}
#endregion
}
public class Bar : Foo
{
public string A
{
get { return "A"; }
}
[Writeable()]
public string B
{
get { return "B"; }
}
[Writeable()]
public string C
{
get { return "C"; }
}
// Snip
}
Please note the attributes marking a couple of the properties, as these are the properties that will get added to the list. This IList will then be used elsewhere during some file write operations.
It is important to me that they are ordered in the list in the order they appear in the code file.
However, MSDN states:
The GetProperties method does not return properties in a particular
order, such as alphabetical or declaration order. Your code must not
depend on the order in which properties are returned, because that
order varies.
So, what is the best way to ensure each PropertyInfo gets added in the order I would like to to be?
(I am also using .NET2.0, so I can't use any Linq goodness, should there be any that would help, although it would be interesting to see.)
Add information to the attribute about the ordering, you can then use this to ensure the ordering, e.g.:
[Writeable(Order = 1)]
So for the following attribute:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class WriteableAttribute : Attribute
{
public int Order { get; set; }
}
You can get an ordered selection of the properties as follows:
private readonly List<PropertyInfo> _props;
protected Foo()
{
_props = new List<PropertyInfo>();
var props = new Dictionary<int, PropertyInfo>();
foreach (PropertyInfo p in GetType().GetProperties())
{
if (Attribute.IsDefined(p, typeof(WriteableAttribute)))
{
var attr = (WriteableAttribute)p
.GetCustomAttributes(typeof(WriteableAttribute))[0];
props.Add(attr.Order, p);
}
}
_props.AddRange(props.OrderBy(kvp => kvp.Key).Select(kvp => kvp.Value));
}
NB For production code I would recommend caching the property information (per type for example) as this will be relatively slow if carried out for each instance.
Update - Caching
With some example caching of property lookup and ordering:
public static class PropertyReflector
{
private static readonly object SyncObj = new object();
private static readonly Dictionary<Type, List<PropertyInfo>> PropLookup =
new Dictionary<Type, List<PropertyInfo>>();
public static IList<PropertyInfo> GetWritableProperties(Type type)
{
lock (SyncObj)
{
List<PropertyInfo> props;
if (!PropLookup.TryGetValue(type, out props))
{
var propsOrder = new Dictionary<int, PropertyInfo>();
foreach (PropertyInfo p in type.GetProperties())
{
if (Attribute.IsDefined(p, typeof(WriteableAttribute)))
{
var attr = (WriteableAttribute)p.GetCustomAttributes(
typeof(WriteableAttribute), inherit: true)[0];
propsOrder.Add(attr.Order, p);
}
}
props = new List<PropertyInfo>(propsOrder
.OrderBy(kvp => kvp.Key)
.Select(kvp => kvp.Value));
PropLookup.Add(type, props);
}
return props;
}
}
}
Update - No Linq
You can replace the Linq section with the following code to order the properties and add them to the cache:
List<int> order = new List<int>(propsOrder.Keys);
order.Sort();
props = new List<PropertyInfo>();
order.ForEach(i => props.Add(propsOrder[i]));
PropLookup.Add(type, props);
Update - Full Linq
And using a fully Linq solution:
static IList<PropertyInfo> GetWritableProperties(Type type)
{
lock (SyncObj)
{
List<PropertyInfo> props;
if (!PropLookup.TryGetValue(type, out props))
{
props = type.GetProperties()
.Select(p => new { p, Atts = p.GetCustomAttributes(typeof(WriteableAttribute), inherit: true) })
.Where(p => p.Atts.Length != 0)
.OrderBy(p => ((WriteableAttribute)p.Atts[0]).Order)
.Select(p => p.p)
.ToList();
PropLookup.Add(type, props);
}
return props;
}
}
A while ago when I had the same problem I wrote a helper class to sort the properties based on the Order property of the attribute. I used the built-in DisplayAttribute but you can just add an Order property to any attribute you write.
class FieldSorter : IComparer, IComparer<DisplayAttribute>, IEqualityComparer<DisplayAttribute>
{
public int Compare(object x, object y)
{
return Compare((DisplayAttribute)x, (DisplayAttribute)y);
}
public int Compare(DisplayAttribute x, DisplayAttribute y)
{
return x.Order.CompareTo(y.Order);
}
public bool Equals(DisplayAttribute x, DisplayAttribute y)
{
return Compare(x, y) == 0;
}
public int GetHashCode(DisplayAttribute obj)
{
return obj.GetHashCode();
}
public static SortedList<DisplayAttribute, PropertyInfo> GetSortedFields(Type type)
{
PropertyInfo[] props = type.GetProperties();
var sortedProps = new SortedList<DisplayAttribute, PropertyInfo>(props.Length, new FieldSorter());
object[] atts;
int assignedOrder = 1000; // anything without pre-assigned order gets a ridiculously high order value. same for duplicates.
foreach (var prop in props)
{
atts = prop.GetCustomAttributes(typeof(DisplayAttribute), true);
if (atts.Length > 0)
{
var att = (DisplayAttribute)atts[0];
if (!att.GetOrder().HasValue || sortedProps.Keys.Contains(att, new FieldSorter()))
att.Order = assignedOrder++;
sortedProps.Add(att, prop);
}
}
return sortedProps;
}
}
This gives you a SortedList where the key is the attribute and the value is the PropertyInfo. This was because I still needed to access other properties of the attribute.
Example usage:
public class Stats
{
[Display(Name = "Changes", Description = "Changed records.", Order = 8)]
public int RecordsWithChanges { get; set; }
[Display(Name = "Invalid", Description = "Number of invalid records analyzed.", Order = 4)]
public int InvalidRecordCount { get; set; }
[Display(Name = "Valid", Description = "Number of valid records.", Order = 6)]
public int ValidRecordCount { get; set; }
[Display(Name = "Cost", Description = "Number of records with a Cost value.", Order = 10)]
public int RecordsWithCost { get; set; }
public Stats(int changed, int valid, int invalid, int cost)
{
RecordsWithChanges = changed;
ValidRecordCount = valid;
InvalidRecordCount = invalid;
RecordsWithCost = cost;
}
}
class Program
{
static void Main(string[] args)
{
var foo = new Stats(123, 456, 7, 89);
var fields = FieldSorter.GetSortedFields(foo.GetType());
foreach (DisplayAttribute att in fields.Keys)
Console.WriteLine("{0}: {1} ({2}) == {3}",
att.Order, att.Name, att.Description, fields[att].GetValue(foo, null));
null));
}
}
Output:
4: Invalid (Number of invalid records analyzed.) -- 7
6: Valid (Number of valid records.) -- 456
8: Changes (Changed records.) -- 123
10: Cost (Number of records with a Cost value.) -- 89