I am trying to build a generic method for exporting a list to excel. An object will have attributes if the property should be printed. ie:
public class someObject {
public int DontPrint {get; set;}
[ExcelAttributes(PrintMe = true)]
public int PrintMe {get; set;}
[ExcelAttributes(PrintMe = true)]
public int PrintMeToo {get; set;}
}
I need a generic way to examine a List and return a printable object. something like the following.
public AppendCell<T>(List<T> list)
var obj = list[0];
PropertyInfo[] propertyInfos;
propertyInfos = obj.GetType().GetProperties(BindingFlags.Public |
BindingFlags.Instance);
foreach (T list1 in list)
{
foreach (PropertyInfo info in propertyInfos)
{
object[] customAttr = info.GetCustomAttributes(true);
// create cell with data
foreach (object o in customAttr)
{
ExcelAttributes ea = o as ExcelAttributes;
if (ea != null && ea.PrintMe ==true)
Cell c = new Cell(info.GetValue(list1,null).ToString())
}
}
}
return c;
}
So...I basically want to be able to examine a list of objects, get the printable properties based on the value of an attribute and print the values for the printable property.
if we create a list of someObject with the values
{DontPrint = 0, PrintMe = 1, PrintMeToo = 2}
{DontPrint = 0, PrintMe = 4, PrintMeToo = 5}
{DontPrint = 0, PrintMe = 3, PrintMeToo = 8}
I would expect to see:
1 2
4 5
3 8
Code similar to what is posted does what I need. Is there a more concise way to get a list of the properties that have the PrintMe attribute, then iterate through the list and act upon those properties?
Isn't it a better idea to create an interface IPrintable which has a member method which returns you a collection of printable properties ?
For instance, something like this:
interface IPrintable
{
ICollection<PrintProperties> GetPrintableProperties();
}
where the PrintProperties type consists for instance out of 2 members (a name & a value).
?
Then, you could just implement this interface to your classes for which you would like to have this behaviour.
But, if you just want to stick with your solution, and you want to have a shorter way to write this, you could perhaps take a look at LINQ.
I believe something like this, should do the trick as well (not tested):
var printableProperties = obj.GetType().GetProperties().Where (pi => Attribute.IsDefined (pi, typeof(PrintableAttribute)).ToList();
see code comments for details ...
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication5 {
internal class Program {
static void Main(string[] args) {
List<someObject> myList = new List<someObject>();
myList.Add(new someObject() {
DontPrint = 0,
PrintMe = 1,
PrintMeToo = 2
});
myList.Add(new someObject() {
DontPrint = 0,
PrintMe = 4,
PrintMeToo = 5
});
myList.Add(new someObject() {
DontPrint = 0,
PrintMe = 3,
PrintMeToo = 8
});
string[,] myPrintables = GetPrintables(myList);
System.Console.ReadKey();
}
public class someObject {
public int DontPrint {
get;
set;
}
[ExcelAttributes( true)]
public int PrintMe {
get;
set;
}
[ExcelAttributes(true)]
public int PrintMeToo {
get;
set;
}
}
/// <summary>
///
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="list"></param>
/// <returns>string[,] - very easy to export to excel in array operation</returns>
public static string[,] GetPrintables<T>(System.Collections.Generic.IList<T> list) {
List<System.Reflection.PropertyInfo> discoveredProperties =
new List<System.Reflection.PropertyInfo>();
System.Type listType = typeof(T);
// first get the property infos (not on every iteration)
foreach (System.Reflection.PropertyInfo propertyInfo in listType.GetProperties(
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)) {
// no indexers
if (propertyInfo.GetIndexParameters().Length == 0) {
ExcelAttributes[] attributes = propertyInfo.GetCustomAttributes(typeof(ExcelAttributes), true) as ExcelAttributes[];
// allowmultiple = false hence length e {0;1}
if (attributes != null && attributes.Length == 1) {
if (attributes[0].PrintMe) {
discoveredProperties.Add(propertyInfo);
}
}
}
}
int numberOfDiscoveredProperties = discoveredProperties.Count;
// can be instantiated only if there are discovered properties, but null may be returned
string[,] arrayItems = new string[list.Count, numberOfDiscoveredProperties];
// if we have any printables
if (numberOfDiscoveredProperties > 0) {
for (int iItem = 0; iItem < list.Count; iItem++) {
for (int iProperty = 0; iProperty < numberOfDiscoveredProperties; iProperty++) {
object value = discoveredProperties[iProperty].GetValue(list[iItem], null);
// value.ToString may not be ideal, perhaps also cache StringConverters
arrayItems[iItem, iProperty] = value != null ? value.ToString() : string.Empty;
}
}
}
return arrayItems;
}
}
[System.AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class ExcelAttributes : System.Attribute {
// use readonly field
private readonly bool _PrintMe;
public ExcelAttributes(bool printMe) {
_PrintMe = printMe;
}
public bool PrintMe {
get {
return _PrintMe;
}
}
}
}
Related
As part of a test I am trying to compare similar objects using reflection.
The objects may or may not have multiple levels of nested params.
For example:
public class Connection{
public string Ip{get; set}
public string Id{get; set}
public string Key{get; set}
public string Transport{get; set}
public Parameters ParametersObj = new Parameters();
public class Parameters
{
public string AssignedName { get; set; }
public string CategoryType { get; set; }
public string Status { get; set; }
}
};
This is just an example of a class, I need this method to deal with any type of object without knowing the number of depth level.
I am doing something like this after I have made sure the two objects are of the same type.
bool result = true;
foreach (var objParam in firstObj.GetType().GetProperties())
{
var value1 = objParam.GetValue(firstObj);
var value2 = objParam.GetValue(secondObj);
if (value1 == null || value2 == null || !value1.Equals(value2))
{
logger.Error("Property: " + objParam.Name);
logger.Error("Values: " + value1?.ToString() + " and " + value2?.ToString());
result = false;
}
}
return result;
It works perfectly for the first level of params but it ignores completely any nested objects. In this example I would like it to compare the values inside the parameters object and if they are different the log to print error "Property: Parameters.Status".
I would recommend to look into some tool which already does that (do not know one which does exactly that but FluentAssertions for example can handle object graph comparisons). But in the nutshell you can check if type is primitive or overrides Equals and call your method recursively. Something like the following:
bool Compare(object firstObj, object secondObj)
{
if (object.ReferenceEquals(firstObj, secondObj))
{
return true;
}
var type = firstObj.GetType();
var propertyInfos = firstObj.GetType().GetProperties();
foreach (var objParam in propertyInfos)
{
var methodInfo = objParam.PropertyType.GetMethod(nameof(object.Equals), BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly, new []{typeof(object)});
var overridesEquals = methodInfo?.GetBaseDefinition().DeclaringType == typeof(object);
var value1 = objParam.GetValue(firstObj);
var value2 = objParam.GetValue(secondObj);
if (value1 == null || value2 == null)
{
// Log
return false;
}
if (object.ReferenceEquals(value1, value2))
{
continue;
}
if (type.IsPrimitive || overridesEquals)
{
if (value1.Equals(value2))
{
continue;
}
// Log?
return false;
}
if (!Compare(value1, value2))
{
// log ?
return false;
}
}
return true;
}
P.S.
Note that Connection.ParametersObj is not a property it is field so it will be ignored by both yours and mine implementations
Consider using source generators instead of reflection.
This does not handle collections.
The problem is that you only do one loop, without looking at the objects within each object. Here's a quick recursive function I threw together. (untested!)
// You can make it non-generic, this just ensures that both arguments are the same type.
static void Go<T>(T left, T right, Action<object, object, PropertyInfo> onFound, int depth = 2)
{
if (left is null)
return;
foreach (var p in left.GetType().GetProperties())
{
var l = p.GetValue(left);
var r = p.GetValue(right);
if (l is null || r is null || !l.Equals(r))
onFound(l, r, p);
if (depth is not 0)
Go(l, r, onFound, depth - 1);
}
}
Usage:
var arg1 = new Connection()
{
ParametersObj = new() { AssignedName = "foo" }
};
var arg2 = new Connection()
{
ParametersObj = new() { AssignedName = "bar" }
};
Go(arg1, arg2, Log); // goes 2 layers deep is you don't specify the last parameter
void Log(object l, object r, PropertyInfo p)
{
logger.Error($"Property: {p.Name}");
logger.Error($"Values: {l} and {r}");
}
I will try to explain my best, but it is a tricky one to explain.
I am having a problem using reflection when a derived object redefines a property already in a base class.
Let's consider the following classes to start with:
// The base class
namespace MyNamesapce
{
public abstract class MyClassBase: IMyClassBase
{
[JsonConstructor]
public MyClassBase()
{
}
public string Info { get; set; }
public string Unit { get; set; }
public string Name { get; set; }
}
}
// MyClassArray is most of the time used
namespace MyNamesapce
{
public class MyClassArray<TType> : MyClassBase, IMyClassArray<TType>
{
public MyClassArray()
{
}
[JsonConstructor]
public MyClassArray(IEnumerable<TType> value, TType minValue, TType maxValue)
{
MinValue = minValue;
MaxValue = maxValue;
Value = value;
}
public IEnumerable<TType> Value { get; set; }
public TType MinValue { get; set; }
public TType MaxValue { get; set; }
}
}
// In some rare cases we need 2D arrays
namespace MyNamesapce
{
public class MyClass2DArray<TType> : MyClassArray<TType>, IMyClass2DArray<TType>
{
private int[] _arraySize { get; set; }
public MyClass2DArray()
{
}
[JsonConstructor]
public MyClass2DArray(TType[][] value, TType minValue, TType maxValue)
{
MinValue = minValue;
MaxValue = maxValue;
_arraySize = new int[2] { value.Count(), value[0].Length };
Value = value;
}
public new TType[][] Value
{
get
{
TType[][] array2D = new TType[_arraySize[0]][];
// Reconstruct the 2D array
TType[] tmpArray;
int startIdx = 0;
for (int numArrays = 0; numArrays < _arraySize[0]; numArrays++)
{
tmpArray = new TType[_arraySize[1]];
Array.Copy(base.Value.ToArray(), startIdx, tmpArray, 0, _arraySize[1]);
startIdx += _arraySize[1];
array2D[numArrays] = tmpArray;
}
return array2D;
}
set
{
// Should not be able to set _value to null
if (value == null)
return;
base.Value = value.SelectMany(v => v).ToArray();
}
}
}
}
I now need to get all the properties from all instances of MyClassArray and MyClassArray2D. You will say, there are plenty of threads discussing that very point, just use "GetType().GetProperties()" for the former and use "GetType().GetProperty(..., BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly)" for the latter.
The problem is that I do not know in advance which class is being processed. In my system when deserialising a Json, instances of both MyClassArray and MyClassArray2D have to be reconstructed, which is done using the following setter:
public static void SetProperty(this Object obj, string propName, Object value)
{
PropertyInfo info = null;
object[] indexer = null;
string[] nameParts = propName.Split('.');
if (obj == null) { return; }
var props = obj.GetType().GetProperties();
for (int idx = 0; idx < nameParts.Count() - 1; idx++)
{
try
{
indexer = null;
// Try to access normal property
info = obj.GetType().GetProperty(nameParts[idx]);
if (info == null)
continue;
obj = info.GetValue(obj, indexer);
}
catch
{
info = null;
indexer = null;
}
}
if (obj != null)
{
// !!! Note that here, using declare only will only work when using derived classes
PropertyInfo propertyToSet = obj.GetType().GetProperty(nameParts.Last(), BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly); // | BindingFlags.DeclaredOnly);
propertyToSet?.SetValue(obj, value);
}
else
{
throw new SystemException($"Could not find the property {propName}");
}
}
As you can see an object is passed in to SetProperty() (that can be of any type).
When it is of type MyClassArray, there are no problems, but if it is of type MyClassArray2D it does not quite work as the latter redefines "Value", which will break the logic as 2 properties called value will exist. I need a way to detect that.
The first loop seems to do the right thing. "obj = info.GetValue(obj, indexer);" will return "obj" containing all the versions of "Value". The problem is in the next part of SetProperty().
How can I detect when more than one "Value" property is in "obj"? And how to always pick the derived version of "Value"?
Also if I just use "BindingFlags.DeclaredOnly" as done here in my code snipet, properties from the base class get lost/disappear, which is undesirable.
Is there maybe a way to return in "obj" all the properties without the duplicates coming from the base class? Or some kind of property filter maybe?
I've a class "TradingStrategy", with n subclasses ("Strategy1, Strategy2 etc...").
I've a simple UI from which i can choose a subclass (I've got all the subclasses of the "TradingStrategy" class pretty easily).
What i want now is to print (in a datagridview, listbox, combobox, doesn't matter) all the public parameters of the choosen subclass.
I would prefer not to instantiate the subclasses.
namespace BackTester
{
class TradingStrategy
{
public string Name;
}
class MA_Test : TradingStrategy
{
new public string Name = System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name;
public int len = 12;
public float lots = 0.1F;
public bool trendFollow = true;
public MA_Test()
{
}
}
class MA_Test2 : TradingStrategy
{
new public string Name = System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name;
public int len = 24;
public float lots = 0.1F;
public bool trendFollow = true;
public MA_Test2()
{
}
}
}
With this code i can insert into a combo box every subclass of "TradingStrategy"
var type = typeof(TradingStrategy);
var types = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(s => s.GetTypes())
.Where(p => type.IsAssignableFrom(p));
foreach (var t in types){
if (t.Name == "TradingStrategy") continue;
boxStrategy.Items.Add(t.Name);
}
I wanna be able to, from the combobox.Text, get all the properties name and values of the corrisponding subclass.
I think I've read (and tried) every post here and in other forum. Many use reflections.
What is the simplest way to get those prop/values?
Thanks
Why not just create an interface ITradingStrategy:
public interface ITradingStrategy
{
string Name { get; }
int len { get; }
float lots { get; }
bool trendFollow { get; }
}
And have all classes inherit from the interface then pull values from interface.
As was mentioned in the comments, you have to instantiate an instance of the class in order to set some values on it.
To get the public fields/properties and their types without instantiating the objects, you can use reflection as follows:
private static Dictionary<string, Type> GetFields(Type t)
{
var fields = new Dictionary<string, Type>();
foreach (var memberInfo in t.GetMembers(BindingFlags.Instance | BindingFlags.Public))
{
var propertyInfo = memberInfo as PropertyInfo;
var fieldInfo = memberInfo as FieldInfo;
if (propertyInfo != null)
{
fields.Add(propertyInfo.Name, propertyInfo.PropertyType);
}
if (fieldInfo != null)
{
fields.Add(fieldInfo.Name, fieldInfo.FieldType);
}
}
return fields;
}
If you already have the object, you can get all the public fields/values with this method.
private static Dictionary<string, object> GetValues(FileInfo o)
{
var values = new Dictionary<string, object>();
foreach (var memberInfo in o.GetType().GetMembers(BindingFlags.Instance | BindingFlags.Public))
{
var propertyInfo = memberInfo as PropertyInfo;
var fieldInfo = memberInfo as FieldInfo;
if (propertyInfo != null)
{
values.Add(propertyInfo.Name, propertyInfo.GetValue(o, null));
}
if (fieldInfo != null)
{
values.Add(fieldInfo.Name, fieldInfo.GetValue(o));
}
}
return values;
}
The following code is a very slow way to get all the types which derive from a given type, due to the way that the CLR implements GetTypes() and the fact there could be thousands of unrelated types in your code which makes the haystack to search even bigger. The only time you should use this method is if you dynamically load assemblies at runtime containing object definitions that you need to load. Unfortunately there is no other way to get this information at runtime:
var type = typeof(TradingStrategy);
var subtypes = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(s => s.GetTypes())
.Where(p => p != type && type.IsAssignableFrom(p));
I would recommend that you store this list of types somewhere in your code, e.g. in an array, and iterate over it when you need to know all of your strategies:
private static readonly Type[] TradingStrategies =
{
typeof(Strategy1),
typeof(Strategy2),
typeof(Strategy3),
};
After reading Erik's answer. If you will never instantiate these classes, you could store this data in a configuration file, and use something like JSON.net to read it, or if you don't want to use an external library, XmlSerializer would work as well. In this case you would store each MATest as a Dictionary (which lends itself nicely to JSON.net's JObject. Using JSON.net, you would have a configuration file that looks like:
[
{
"MA_Test": {
"len": 12,
"lots": 0.1,
"trendFollow": true
},
"MA_Test2": {
"len": 24,
"lots": 0.1,
"trendFollow": true
}
}
]
Then read it with code that looks like:
public JObject ReadConfig(string configPath)
{
using (var filestream = File.Open(configPath, FileMode.Open))
using (var streamReader = new StreamReader(filestream))
using (var jsonTextReader = new JsonTextReader(streamReader))
{
var jsonSerializer = new JsonSerializer();
return jsonSerializer.Deserialize<JObject>(jsonTextReader);
}
}
Thank you all for you answers.
The simplest way I found to get the properties from an indirected instantiated class is this:
var strategy = activator.CreateInstance(Type.GetType("BackTester."+boxStrategy.Text));
foreach (FieldInfo prop in strategy.GetType().GetFields(BindingFlags.Public
| BindingFlags.Instance))
{
listBox1.Items.Add(prop.ToString() + " " + prop.GetValue(strategy));
}
Based on the code you've provided, there is no reason for there to be separate classes for each MA_Test (X DO NOT use underscores, hyphens, or any other nonalphanumeric characters.). Instead these should be the same class with different properties (not fields).
class TradingStrategy
{
public string Name { get; set; }
}
class MATest : TradingStrategy
{
// this is not needed if it is inherited by TradingStragegy
// You should be getting a warning that you are hiding
// the field/property
// public string Name { get; set; }
// Should probably be more descriptive
// e.g. LengthInFeet...
public int Length { get; set; }
public float Lots { get; set; }
// I recommended boolean properties to be prefixed with
// Is, Can, Has, etc
public bool CanTrendFollow { get; set; }
}
// Somewhere Else...
var MATests = new List<MATest>()
{
new MATest()
{
Name = "MATest",
Length = 12,
Lots = 0.1F,
CanTrendFollow = true
},
new MATest()
{
Name = "MATest",
Length = 24,
Lots = 0.1F,
CanTrendFollow = true
},
}
Now instead of costly Reflection and Activator, just create the list classes once (manually, from config or even a database), and they can be used for whatever you need.
I have the following ENUM:
[Flags]
public enum DataFiat {
[Description("Público")]
Public = 1,
[Description("Filiado")]
Listed = 2,
[Description("Cliente")]
Client = 4
} // DataFiat
And I created an extension to get an Enum attribute:
public static T GetAttribute<T>(this Enum value) where T : Attribute {
T attribute;
MemberInfo info = value.GetType().GetMember(value.ToString()).FirstOrDefault();
if (info != null) {
attribute = (T)info.GetCustomAttributes(typeof(T), false).FirstOrDefault();
return attribute;
}
return null;
}
This works for non Flags Enums ... But when I have:
var x = DataFiat.Public | DataFiat.Listed;
var y = x.GetAttribute<Description>();
The value of y is null ...
I would like to get "Público, Filiado, Cliente" ... Just as ToString() works.
How can I change my extension to make this work?
Thank You
You can use this:
var values = x.ToString()
.Split(new[] { ", " }, StringSplitOptions.None)
.Select(v => (DataFiat)Enum.Parse(typeof(DataFiat), v));
To get the individual values. Then get the attribute values of them.
Something like this:
var y2 = values.GetAttributes<DescriptionAttribute, DataFiat>();
public static T[] GetAttributes<T, T2>(this IEnumerable<T2> values) where T : Attribute
{
List<T> ts =new List<T>();
foreach (T2 value in values)
{
T attribute;
MemberInfo info = value.GetType().GetMember(value.ToString()).FirstOrDefault();
if (info != null)
{
attribute = (T)info.GetCustomAttributes(typeof(T), false).FirstOrDefault();
ts.Add(attribute);
}
}
return ts.ToArray();
}
in .NET CORE without any additional libraries you can do:
public enum Divisions
{
[Display(Name = "My Title 1")]
None,
[Display(Name = "My Title 2")]
First,
}
and to get the title:
using System.ComponentModel.DataAnnotations
using System.Reflection
string title = enumValue.GetType()?.GetMember(enumValue.ToString())?[0]?.GetCustomAttribute<DisplayAttribute>()?.Name;
I think you want to make something like that
using System;
public enum ArrivalStatus { Unknown=-3, Late=-1, OnTime=0, Early=1 };
public class Example
{
public static void Main()
{
int[] values = { -3, -1, 0, 1, 5, Int32.MaxValue };
foreach (var value in values)
{
ArrivalStatus status;
if (Enum.IsDefined(typeof(ArrivalStatus), value))
status = (ArrivalStatus) value;
else
status = ArrivalStatus.Unknown;
Console.WriteLine("Converted {0:N0} to {1}", value, status);
}
}
}
// The example displays the following output:
// Converted -3 to Unknown
// Converted -1 to Late
// Converted 0 to OnTime
// Converted 1 to Early
// Converted 5 to Unknown
// Converted 2,147,483,647 to Unknown
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
public static class Program
{
[Flags]
public enum DataFiat
{
[Description("Público")]
Public = 1,
[Description("Filiado")]
Listed = 2,
[Description("Cliente")]
Client = 4
}
public static ICollection<string> GetAttribute<T>(this Enum value)
{
var result = new Collection<string>();
var type = typeof(DataFiat);
foreach (var value1 in Enum.GetValues(type))
{
var memInfo = type.GetMember(value1.ToString());
var attributes = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);
var description = ((DescriptionAttribute)attributes[0]).Description;
result.Add(description);
}
return result;
}
static void Main(string[] args)
{
var x = DataFiat.Public | DataFiat.Listed;
var y = x.GetAttribute<DataFiat>();
var output = string.Join(" ", y.ToArray());
Console.WriteLine(output);
}
}
I have changed the T to ICollection but you can change it as you wish or you can merege the data within the method and return the string back.
I came up with a different solution based on my previous code. It can be used as follows:
DataFiat fiat = DataFiat.Public | DataFiat.Listed;
var b = fiat.ToString();
var c = fiat.GetAttributes<TextAttribute>();
var d = fiat.GetAttributes<TextAttribute, String>(x => String.Join(",", x.Select(y => y.Value)));
I think it becomes easy to use either to get the attributes or doing something with them.
What do you think?
Let me know if the code can be somehow improved. Here is the code:
public static List<T> GetAttributes<T>(this Enum value) where T : Attribute {
List<T> attributes = new List<T>();
IEnumerable<Enum> flags = Enum.GetValues(value.GetType()).Cast<Enum>().Where(value.HasFlag);
if (flags != null) {
foreach (Enum flag in flags) {
MemberInfo info = flag.GetType().GetMember(flag.ToString()).FirstOrDefault();
if (info != null)
attributes.Add((T)info.GetCustomAttributes(typeof(T), false).FirstOrDefault());
}
return attributes;
}
return null;
} // GetAttributes
public static Expected GetAttributes<T, Expected>(this Enum value, Func<List<T>, Expected> expression) where T : Attribute {
List<T> attributes = value.GetAttributes<T>();
if (attributes == null)
return default(Expected);
return expression(attributes);
} // GetAttributes
I want to get the names of all properties that changed for matching objects. I have these (simplified) classes:
public enum PersonType { Student, Professor, Employee }
class Person {
public string Name { get; set; }
public PersonType Type { get; set; }
}
class Student : Person {
public string MatriculationNumber { get; set; }
}
class Subject {
public string Name { get; set; }
public int WeeklyHours { get; set; }
}
class Professor : Person {
public List<Subject> Subjects { get; set; }
}
Now I want to get the objects where the Property values differ:
List<Person> oldPersonList = ...
List<Person> newPersonList = ...
List<Difference> = GetDifferences(oldPersonList, newPersonList);
public List<Difference> GetDifferences(List<Person> oldP, List<Person> newP) {
//how to check the properties without casting and checking
//for each type and individual property??
//can this be done with Reflection even in Lists??
}
In the end I would like to have a list of Differences like this:
class Difference {
public List<string> ChangedProperties { get; set; }
public Person NewPerson { get; set; }
public Person OldPerson { get; set; }
}
The ChangedProperties should contain the name of the changed properties.
I've spent quite a while trying to write a faster reflection-based solution using typed delegates. But eventually I gave up and switched to Marc Gravell's Fast-Member library to achieve higher performance than with normal reflection.
Code:
internal class PropertyComparer
{
public static IEnumerable<Difference<T>> GetDifferences<T>(PropertyComparer pc,
IEnumerable<T> oldPersons,
IEnumerable<T> newPersons)
where T : Person
{
Dictionary<string, T> newPersonMap = newPersons.ToDictionary(p => p.Name, p => p);
foreach (T op in oldPersons)
{
// match items from the two lists by the 'Name' property
if (newPersonMap.ContainsKey(op.Name))
{
T np = newPersonMap[op.Name];
Difference<T> diff = pc.SearchDifferences(op, np);
if (diff != null)
{
yield return diff;
}
}
}
}
private Difference<T> SearchDifferences<T>(T obj1, T obj2)
{
CacheObject(obj1);
CacheObject(obj2);
return SimpleSearch(obj1, obj2);
}
private Difference<T> SimpleSearch<T>(T obj1, T obj2)
{
Difference<T> diff = new Difference<T>
{
ChangedProperties = new List<string>(),
OldPerson = obj1,
NewPerson = obj2
};
ObjectAccessor obj1Getter = ObjectAccessor.Create(obj1);
ObjectAccessor obj2Getter = ObjectAccessor.Create(obj2);
var propertyList = _propertyCache[obj1.GetType()];
// find the common properties if types differ
if (obj1.GetType() != obj2.GetType())
{
propertyList = propertyList.Intersect(_propertyCache[obj2.GetType()]).ToList();
}
foreach (string propName in propertyList)
{
// fetch the property value via the ObjectAccessor
if (!obj1Getter[propName].Equals(obj2Getter[propName]))
{
diff.ChangedProperties.Add(propName);
}
}
return diff.ChangedProperties.Count > 0 ? diff : null;
}
// cache for the expensive reflections calls
private Dictionary<Type, List<string>> _propertyCache = new Dictionary<Type, List<string>>();
private void CacheObject<T>(T obj)
{
if (!_propertyCache.ContainsKey(obj.GetType()))
{
_propertyCache[obj.GetType()] = new List<string>();
_propertyCache[obj.GetType()].AddRange(obj.GetType().GetProperties().Select(pi => pi.Name));
}
}
}
Usage:
PropertyComparer pc = new PropertyComparer();
var diffs = PropertyComparer.GetDifferences(pc, oldPersonList, newPersonList).ToList();
Performance:
My very biased measurements showed that this approach is about 4-6 times faster than the Json-Conversion and about 9 times faster than ordinary reflections. But in fairness, you could probably speed up the other solutions quite a bit.
Limitations:
At the moment my solution doesn't recurse over nested lists, for example it doesn't compare individual Subject items - it only detects that the subjects lists are different, but not what or where. However, it shouldn't be too hard to add this feature when you need it. The most difficult part would probably be to decide how to represent these differences in the Difference class.
We start with 2 simple methods:
public bool AreEqual(object leftValue, object rightValue)
{
var left = JsonConvert.SerializeObject(leftValue);
var right = JsonConvert.SerializeObject(rightValue);
return left == right;
}
public Difference<T> GetDifference<T>(T newItem, T oldItem)
{
var properties = typeof(T).GetProperties();
var propertyValues = properties
.Select(p => new {
p.Name,
LeftValue = p.GetValue(newItem),
RightValue = p.GetValue(oldItem)
});
var differences = propertyValues
.Where(p => !AreEqual(p.LeftValue, p.RightValue))
.Select(p => p.Name)
.ToList();
return new Difference<T>
{
ChangedProperties = differences,
NewItem = newItem,
OldItem = oldItem
};
}
AreEqual just compares the serialized versions of two objects using Json.Net, this keeps it from treating reference types and value types differently.
GetDifference checks the properties on the passed in objects and compares them individually.
To get a list of differences:
var oldPersonList = new List<Person> {
new Person { Name = "Bill" },
new Person { Name = "Bob" }
};
var newPersonList = new List<Person> {
new Person { Name = "Bill" },
new Person { Name = "Bobby" }
};
var diffList = oldPersonList.Zip(newPersonList, GetDifference)
.Where(d => d.ChangedProperties.Any())
.ToList();
Everyone always tries to get fancy and write these overly generic ways of extracting data. There is a cost to that.
Why not be old school simple.
Have a GetDifferences member function Person.
virtual List<String> GetDifferences(Person otherPerson){
var diffs = new List<string>();
if(this.X != otherPerson.X) diffs.add("X");
....
}
In inherited classes. Override and add their specific properties. AddRange the base function.
KISS - Keep it simple. It would take you 10 minutes of monkey work to write it, and you know it will be efficient and work.
I am doing it by using this:
//This structure represents the comparison of one member of an object to the corresponding member of another object.
public struct MemberComparison
{
public static PropertyInfo NullProperty = null; //used for ROOT properties - i dont know their name only that they are changed
public readonly MemberInfo Member; //Which member this Comparison compares
public readonly object Value1, Value2;//The values of each object's respective member
public MemberComparison(PropertyInfo member, object value1, object value2)
{
Member = member;
Value1 = value1;
Value2 = value2;
}
public override string ToString()
{
return Member.name+ ": " + Value1.ToString() + (Value1.Equals(Value2) ? " == " : " != ") + Value2.ToString();
}
}
//This method can be used to get a list of MemberComparison values that represent the fields and/or properties that differ between the two objects.
public static List<MemberComparison> ReflectiveCompare<T>(T x, T y)
{
List<MemberComparison> list = new List<MemberComparison>();//The list to be returned
if (x.GetType().IsArray)
{
Array xArray = x as Array;
Array yArray = y as Array;
if (xArray.Length != yArray.Length)
list.Add(new MemberComparison(MemberComparison.NullProperty, "array", "array"));
else
{
for (int i = 0; i < xArray.Length; i++)
{
var compare = ReflectiveCompare(xArray.GetValue(i), yArray.GetValue(i));
if (compare.Count > 0)
list.AddRange(compare);
}
}
}
else
{
foreach (PropertyInfo m in x.GetType().GetProperties())
//Only look at fields and properties.
//This could be changed to include methods, but you'd have to get values to pass to the methods you want to compare
if (!m.PropertyType.IsArray && (m.PropertyType == typeof(String) || m.PropertyType == typeof(double) || m.PropertyType == typeof(int) || m.PropertyType == typeof(uint) || m.PropertyType == typeof(float)))
{
var xValue = m.GetValue(x, null);
var yValue = m.GetValue(y, null);
if (!object.Equals(yValue, xValue))//Add a new comparison to the list if the value of the member defined on 'x' isn't equal to the value of the member defined on 'y'.
list.Add(new MemberComparison(m, yValue, xValue));
}
else if (m.PropertyType.IsArray)
{
Array xArray = m.GetValue(x, null) as Array;
Array yArray = m.GetValue(y, null) as Array;
if (xArray.Length != yArray.Length)
list.Add(new MemberComparison(m, "array", "array"));
else
{
for (int i = 0; i < xArray.Length; i++)
{
var compare = ReflectiveCompare(xArray.GetValue(i), yArray.GetValue(i));
if (compare.Count > 0)
list.AddRange(compare);
}
}
}
else if (m.PropertyType.IsClass)
{
var xValue = m.GetValue(x, null);
var yValue = m.GetValue(y, null);
if ((xValue == null || yValue == null) && !(yValue == null && xValue == null))
list.Add(new MemberComparison(m, xValue, yValue));
else if (!(xValue == null || yValue == null))
{
var compare = ReflectiveCompare(m.GetValue(x, null), m.GetValue(y, null));
if (compare.Count > 0)
list.AddRange(compare);
}
}
}
return list;
}
Here you have a code which does what you want with Reflection.
public List<Difference> GetDifferences(List<Person> oldP, List<Person> newP)
{
List<Difference> allDiffs = new List<Difference>();
foreach (Person oldPerson in oldP)
{
foreach (Person newPerson in newP)
{
Difference curDiff = GetDifferencesTwoPersons(oldPerson, newPerson);
allDiffs.Add(curDiff);
}
}
return allDiffs;
}
private Difference GetDifferencesTwoPersons(Person OldPerson, Person NewPerson)
{
MemberInfo[] members = typeof(Person).GetMembers();
Difference returnDiff = new Difference();
returnDiff.NewPerson = NewPerson;
returnDiff.OldPerson = OldPerson;
returnDiff.ChangedProperties = new List<string>();
foreach (MemberInfo member in members)
{
if (member.MemberType == MemberTypes.Property)
{
if (typeof(Person).GetProperty(member.Name).GetValue(NewPerson, null).ToString() != typeof(Person).GetProperty(member.Name).GetValue(OldPerson, null).ToString())
{
returnDiff.ChangedProperties.Add(member.Name);
}
}
}
return returnDiff;
}