using reflection to compare object in list - c#

I have a 'Invoice' WinForm C# where it contains the usual textboxes and an unbound datagridview.
I used a 'Invoice' class object to store the form fields' values. The datagridview rows are stored to a List < SubInvoice> properties in the 'Invoice'.
public static bool CompareObjects(object original, object altered)
{
Type o = original.GetType();
Type a = altered.GetType();
foreach (PropertyInfo p in o.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
Console.WriteLine("Original: {0} = {1}", p.Name, p.GetValue(original, null));
Console.WriteLine("Altered: {0} = {1}", p.Name, p.GetValue(altered, null));
if (p.GetValue(original, null).ToString() != p.GetValue(altered, null).ToString())
{
//Console.WriteLine("Not Equal");
return false;
}
}
return true;
}
I am using a global class with a method that uses Reflection to loop through the original 'invoice' object and compared it with the altered 'invoice'. If there is any difference, the user will be notified to save the invoice.
The above method works fine with all the properties of 'Invoice' class but I do not know how to check for the List in which it stores the value of each datagridrow into a 'SubInvoice' class object that stored it as an object in the list.
Can someone please give me some help here? I have also checked for similar thread in stackoverflow and other forums but in vain.
Update: I need to create a global generic method that will check for all type of classes. It can be 'Invoice', 'Customer'. The purpose is to track any changes made to the form at any particular instance and prompt the user to save.

thank you all for your help.
I have stumbled into the following article while still eager to create a global generic method through reflection.
http://www.c-sharpcorner.com/UploadFile/1a81c5/custom-extension-method-to-compare-list-in-C-Sharp/
Here's the code if anyone is interested.
public static bool CompareObjects(object original, object altered)
{
bool result = true;
//Get the class
Type o = original.GetType();
Type a = altered.GetType();
//Cycle through the properties.
foreach (PropertyInfo p in o.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
Console.WriteLine("Original: {0} = {1}", p.Name, p.GetValue(original, null));
Console.WriteLine("Altered: {0} = {1}", p.Name, p.GetValue(altered, null));
if (!p.PropertyType.IsGenericType)
{
if (p.GetValue(original, null) != null && p.GetValue(altered, null) != null)
{
if (!p.GetValue(original, null).ToString().Equals(p.GetValue(altered, null).ToString()))
{
result = false;
break;
}
}
else
{
//If one is null, the other is not
if ((p.GetValue(original, null) == null && p.GetValue(altered, null) != null) || (p.GetValue(original, null) != null && p.GetValue(altered, null) == null))
{
result = false;
break;
}
}
}
}
return result;
}
public static bool CompareLists<T>(this List<T> original, List<T> altered)
{
bool result = true;
if (original.Count != altered.Count)
{
return false;
}
else
{
if (original != null && altered != null)
{
foreach (T item in original)
{
T object1 = item;
T object2 = altered.ElementAt(original.IndexOf(item));
Type objectType = typeof(T);
if ((object1 == null && object2 != null) || (object1 != null && object2 == null))
{
result = false;
break;
}
if (!CompareObjects(object1, object2))
{
result = false;
break;
}
}
}
else
{
if ((original == null && altered != null) || (original != null && altered == null))
{
return false;
}
}
}
return result;
}
Usage: if (!xx.CompareObjects(searchedInvoice, alteredInvoice) | !xx.CompareLists(searchedInvoice.SubInvoice, alteredInvoice.SubInvoice))

If you only whant to inform the user to save the pending changes then you should implement INotifyPropertyChanged instead of doing some black magic.

One trick I've seen before is to mark the objects as serializable, then serialize them in to strings and compare the strings. It may not be the most efficient method, but it is quick, easy, and works for almost any kind of data class. It even works for "complex" nested classes that have references to other classes.
One down-side is that it won't easily tell you what specifically changed, just that something is different.

Related

How to overcome foreach loop for list object dynamically

I'm swapping my values in List Object on some conditions and update List Object value.
Currently, what I'm doing is
- Looping on each object through List
- Check If condition is net
- Swap values
public static void SwapMinMaxIfNull<T>(this IEnumerable<T> rows, string reportfor)
{
if (reportfor.Equals("Comparison"))
{
var row = rows as IEnumerable<RiskBoardDataToExport>;
try
{
if (rows.Any())
{
var Tests = row.Where(min => min.MinGaitSpeed == null && min.MaxGaitSpeed != null).ToList();
if (Tests != null)
{
foreach (RiskBoardDataToExport test in Tests)
{
test.MinGaitSpeed = test.MaxGaitSpeed;
test.MaxGaitSpeed = null;
}
}
// again check for next object
Tests = row.Where(min => min.MinTUGTime == null && min.MaxTUGTime != null).ToList();
if (Tests != null)
{
foreach (RiskBoardDataToExport test in Tests)
{
test.MinTUGTime = test.MaxTUGTime;
test.MaxTUGTime = null;
}
}
// again check for next object
Tests = row.Where(min => min.MinBergScoreSpeed == null && min.MaxBergScoreSpeed != null).ToList();
if (Tests != null)
{
foreach (RiskBoardDataToExport test in Tests)
{
test.MinBergScoreSpeed = test.MaxBergScoreSpeed;
test.MaxBergScoreSpeed = null;
}
}
//.. for brevity
}
}
}
Can I do it in better way? I know about PropertyInfo i.e. Can check property name and get value etc, but, not having any hint to get this done.
Thanks
It's not exactly what you're asking for, but you can combine the clauses in your Where statements and then have a few if statements in the body:
public static void SwapMinMaxIfNull(this IEnumerable<RiskBoardDataToExport> rows,
string reportfor)
{
if (rows = null) return;
if (reportfor.Equals("Comparison", StringComparison.OrdinalIgnoreCase))
{
foreach (var row in rows.Where(r =>
(r.MinGaitSpeed == null && r.MaxGaitSpeed != null) ||
(r.MinBergScoreSpeed == null && r.MaxBergScoreSpeed != null) ||
(r.MinBergScoreSpeed == null && r.MaxBergScoreSpeed != null)))
{
if (row.MinGaitSpeed == null)
{
row.MinGaitSpeed = row.MaxGaitSpeed;
row.MaxGaitSpeed = null;
}
if (row.MinTUGTime == null)
{
row.MinTUGTime = row.MaxTUGTime;
row.MaxTUGTime = null;
}
if (row.MinBergScoreSpeed == null)
{
row.MinBergScoreSpeed = row.MaxBergScoreSpeed;
row.MaxBergScoreSpeed = null;
}
}
}
}
As this is an operation where order of the items in the list does not matter, you can easily speed this up by parallelization (you can read up on that here).
So, what you should do, is handle this foreach loop in a parallel way and combine it with Rufus L's optimized code for the fastest result.
var rows = rows.Where(r =>
(r.MinGaitSpeed == null && r.MaxGaitSpeed != null) ||
(r.MinBergScoreSpeed == null && r.MaxBergScoreSpeed != null) ||
(r.MinBergScoreSpeed == null && r.MaxBergScoreSpeed != null))
Parallel.ForEach(rows, (row) => {
{
if (row.MinGaitSpeed == null)
{
row.MinGaitSpeed = row.MaxGaitSpeed;
row.MaxGaitSpeed = null;
}
if (row.MinTUGTime == null)
{
row.MinTUGTime = row.MaxTUGTime;
row.MaxTUGTime = null;
}
if (row.MinBergScoreSpeed == null)
{
row.MinBergScoreSpeed = row.MaxBergScoreSpeed;
row.MaxBergScoreSpeed = null;
}
}
Note that this requires the System.Threading.Tasks namespace, that's where the Parallel class is.

copy properties c# but check if a property is already set

I working hard on a JSON-Deserializer on my own. Just for training. I am nearly finished, but I have a copying issue.
I already have this:
public void CopyValues<T>(T target, T source)
{
Type t = typeof(T);
var properties = t.GetProperties().Where(prop => prop.CanRead && prop.CanWrite);
foreach (var prop in properties)
{
var value = prop.GetValue(source, null);
if (value != null)
prop.SetValue(target, value, null);
}
}
The Main Problem is here. I have a Property containing 2 Properties. Like content.link and content.value.
If I use the Copy Function it copys right. No discussion. But if I put the copy function into a loop, and the data is filled up, the source having also
"content", but without link and value.
If I copy again, the already correctly filled properties are getting overridden, and the result is that I have only null at conent.link and content.value.
Is there a way, to check if link and value is set to null ?
In order to copy the nested properties, you will need use a recursive function:
public static void DeepCopy<T>(T target, T source)
{
DeepCloneImpl(typeof(T), source, target);
}
public static T DeepClone<T>(T template)
where T : new()
{
return (T)DeepCloneImpl(typeof(T), template);
}
private static object DeepCloneImpl(Type type, object template, object stump = null)
{
if (template == null)
{
return null;
}
var clone = stump ?? Activator.CreateInstance(type);
var clonableProperties = type.GetProperties()
.Where(x => x.GetMethod != null && x.SetMethod != null);
foreach (var property in clonableProperties)
{
var propertyType = property.PropertyType;
if (propertyType.GetTypeInfo().IsValueType || propertyType == typeof(string))
{
var value = property.GetValue(template);
property.SetValue(clone, value);
}
else if (propertyType.GetTypeInfo().IsClass && propertyType.GetConstructor(Type.EmptyTypes) != null)
{
var value = DeepCloneImpl(propertyType, property.GetValue(template));
property.SetValue(clone, value);
}
else if (propertyType.IsArray)
{
var source = property.GetValue(template) as Array;
if (source == null)
{
continue;
}
var elementType = propertyType.GetElementType();
if (elementType.GetTypeInfo().IsValueType || elementType == typeof(string))
{
var copies = Array.CreateInstance(elementType, source.Length);
Array.Copy(source, copies, source.Length);
property.SetValue(clone, copies);
}
else if (elementType.GetTypeInfo().IsClass)
{
var copies = Array.CreateInstance(elementType, source.Length);
for (int i = 0; i < source.Length; i++)
{
var copy = DeepCloneImpl(elementType, source.GetValue(i));
copies.SetValue(copy, i);
}
property.SetValue(clone, copies);
}
}
}
return clone;
}
This should cover most of the use case, however you will have to handle self/circular references.
From what I can gather, the problem is values are being overridden. And from looking at your method, it appears to be doing what you'd expect, it's a soft clone of property values. Depending on what a correctly filled property is in your situation, you'll have to make a comparison that prevents the property from being set again. Perhaps value != null should actually be value == null so that you're only setting the value when there wasn't already one. Because of the generic typing of this method you don't have to worry about property mismatches if that's why you're using != null. Both arguments, source and target, will be of the same type.

Looping through Generic Class's collection property

Although many questions have been posted, none seem to help me on my issue.
I've started a new Generics / Reflection adventure and I'm just trying to get my head around the syntax and concepts.
I have a Generic class with X amount of properties and one being a collection, all is working fine but I'm having problems extracting the values from the collection props by property name.
foreach (var property in typeof(T).GetProperties())
{
if (property.Name == "Props")
{
foreach (var item in (IEnumerable)property.GetValue(type, null))
{
var propertyName = "";
var newValue = "";
var oldValue = "";
sbDescription.AppendLine(strDescriptionVals
.Replace("{0}", (item.ToString() == "PropertyName") ? item.ToString() : "" + ", "));
sbAllNotes.AppendLine(strAllNotes
.Replace("{0}", (item.ToString() == "PropertyName") ? item.ToString() : "")
.Replace("{1}", (item.ToString() == "NewValue") ? item.ToString() : "")
.Replace("{2}", (item.ToString() == "OldValue") ? item.ToString() : ""));
}
}
}
As you can see I've pinpointed the property Props and now I want to loop through that and pull values by property name.
item.ToString() just prints the namespace of the class property and not the value
Hoping you kind folk can point me in the right direction?
You probably want to recursively "dive" into the items properties.
Note, only conceptual code fragments, no guarantee that it works as I write it here:
void Print(object value, string propertyName)
{
if (property.PropertyType == typeof(string) || property.PropertyType.IsPrimitive)
{
sbAllNotes.AppnedLine(.. propertyName .. value ..);
}
else if ((typeof(IEnumerable).IsAssignableFrom(property.PropertyType)
{
PrintCollectioType((IEnumerable)value);
}
else
{
PrintComplexType(value);
}
}
void PrintCollectioType(IEnumerable collection)
{
foreach (var item in collection)
{
Print(item, "Collection Item");
}
}
void PrintComplexType(IEnumerable collection)
{
foreach(var property in owner.GetType().GetProperties())
{
var propertyName = property.Name;
var propertyValue = property.GetValue(owner);
Print(item, propertyName);
}
}
To print the "Props", do:
var props = typeof(T).GetProperty("Props");
var propsValue = props.GetValue(rootObject);
Print(propsValue, "Root");
After playing with the code and understanding the logic a little more, it occurred to me that is was as simple as "Getting the type and Property" on the Props iteration:
//Get the collection property
foreach (var property in typeof(T).GetProperties())
{
if (property.Name == "Props")
{
foreach (var item in (IEnumerable)property.GetValue(type, null))
{
//Because props is a collection, Getting the type and property on each pass was essential
var propertyName = item.GetType().GetProperty("PropertyName").GetValue(item, null);
var newValue = item.GetType().GetProperty("NewValue").GetValue(item, null);
var oldValue = item.GetType().GetProperty("OldValue").GetValue(item, null);
sbDescription.AppendLine(strDescriptionVals
.Replace("{0}", (propertyName != null) ? propertyName.ToString() : "" + ", "));
sbAllNotes.AppendLine(strAllNotes
.Replace("{0}", (propertyName != null) ? propertyName.ToString() : "")
.Replace("{1}", (newValue != null) ? newValue.ToString() : "")
.Replace("{2}", (oldValue != null) ? oldValue.ToString() : ""));
}
}
}

C# - GetValue - Object Does Not Match Target Type

I am trying to write a generic method to compare two objects (I purposefully have two different types coming in. The second one has the same properties as the first one. The first just has more.).
I want to make sure the properties have the same values. The following code works for most of the properties that I have in the objects, but occasionally it throws the:
"Object Does Not Match Target Type"
...error at
var valFirst = prop.GetValue(manuallyCreated, null) as IComparable;
public static bool SameCompare<T, T2>(T manuallyCreated, T2 generated){
var propertiesForGenerated = generated.GetType().GetProperties();
int compareValue = 0;
foreach (var prop in propertiesForGenerated) {
var valFirst = prop.GetValue(manuallyCreated, null) as IComparable;
var valSecond = prop.GetValue(generated, null) as IComparable;
if (valFirst == null && valSecond == null)
continue;
else if (valFirst == null) {
System.Diagnostics.Debug.WriteLine(prop + "s are not equal");
return false;
}
else if (valSecond == null) {
System.Diagnostics.Debug.WriteLine(prop + "s are not equal");
return false;
}
else if (prop.PropertyType == typeof(System.DateTime)) {
TimeSpan timeDiff = (DateTime)valFirst - (DateTime)valSecond;
if (timeDiff.Seconds != 0) {
System.Diagnostics.Debug.WriteLine(prop + "s are not equal");
return false;
}
}
else
compareValue = valFirst.CompareTo(valSecond);
if (compareValue != 0) {
System.Diagnostics.Debug.WriteLine(prop + "s are not equal");
return false;
}
}
System.Diagnostics.Debug.WriteLine("All values are equal");
return true;
}
Each property defined on a type in .NET is different, even if they have the same name. You'd have to do this:
foreach (var prop in propertiesForGenerated)
{
var otherProp = typeof(T).GetProperty(prop.Name);
var valFirst = otherProp.GetValue(manuallyCreated, null) as IComparable;
var valSecond = prop.GetValue(generated, null) as IComparable;
...
Of course this doesn't take into account that some properties defined on T may not exist on T2, and vice versa.
Because manuallyCreated is not of type T2. Try getting the property object with the same name on that type, and it should work (or if your assumption that all properties in T2 are also in T1, you'll get a better error):
public static bool SameCompare<T, T2>(T manuallyCreated, T2 generated){
var propertiesForGenerated = typeof(T2).GetProperties();
int compareValue = 0;
foreach (var prop in propertiesForGenerated) {
var firstProperty = typeof(T).GetProperty(prop.Name);
var valFirst = firstProperty.GetValue(manuallyCreated, null) as IComparable;
var valSecond = prop.GetValue(generated, null) as IComparable;
...
}
}
I believe it would be better to have Interface of the properties you need to compare,
I would then inherit IEqualityComparer<YourInterface>
and in Equals and GetHashCode
public bool Equals(YourInterface one, YourInterface two)
{
return this.GetHashCode(one) == this.GetHashCode(two);
}
public int GetHashCode(YourInterface test)
{
if(test == null)
{
return 0;
}
int hash = 0;
//// get hash or set int on each property.
return hash;
}

create a copy of an ASP.NET Control object

I have a cached control object form a UserControl in asp.net cache.
I like to access my control and make a copy of it to freely change it without having any affect on the base cached control.
Hi
I found the code that I was looking for.
I put it here maybe it helps you too.
/// <summary>
/// this method makes a copy of object as a clone function
/// </summary>
/// <param name="o"></param>
/// <returns></returns>
public static object Clone_Control(object o)
{
Type type = o.GetType();
PropertyInfo[] properties = type.GetProperties();
Object retObject = type.InvokeMember("", System.Reflection.BindingFlags.CreateInstance, null, o, null);
foreach (PropertyInfo propertyInfo in properties)
{
if (propertyInfo.CanWrite)
{
propertyInfo.SetValue(retObject, propertyInfo.GetValue(o, null), null);
}
}
return retObject;
}
It is certainly possible to do what you are asking to do. And it is very straightforward. Something like this maybe?
private static void ChangeMyControlTextFromCache(string controlName, string newText, Panel containingPanel)
{
MyControl original = ViewState[controlName] as MyControl;
if (original == null)
{
original = new MyControl();
ViewState[controlName] = original;
}
MyControl clone = CloneMyControl(original);
clone.Text = newText;
if (containingPanel.Children.Contains(original))
containingPanel.Children.Remove(original);
containingPanel.Children.Add(clone);
}
private static MyControl CloneMyControl(MyControl original)
{
MyControl clone = new MyControl();
clone.Text = original.Text;
clone.SomeOtherProperty = original.SomeOtherProperty;
return clone;
}
Here is another clone method that will also work with MONO.
It clones the passed control and it's children.
Note: A cloned control cannot be added to a different page (or in a later post-back) even it has not been added to any page yet. It might seem to work on Windows but will crash on MONO so I guess it is generally not meant to be done.
public static Control CloneControl(Control c) {
var clone = Activator.CreateInstance(c.GetType()) as Control;
if (c is HtmlControl) {
clone.ID = c.ID;
foreach (string key in ((HtmlControl)c).Attributes.Keys)
((HtmlControl)clone).Attributes.Add(key, (string)((HtmlControl)c).Attributes[key]);
}
else {
foreach (PropertyInfo p in c.GetType().GetProperties()) {
// "InnerHtml/Text" are generated on the fly, so skip them. "Page" can be ignored, because it will be set when control is added to a Page.
if (p.CanRead && p.CanWrite && p.Name != "InnerHtml" && p.Name != "InnerText" && p.Name != "Page") {
try {
p.SetValue(clone, p.GetValue(c, p.GetIndexParameters()), p.GetIndexParameters());
}
catch {
}
}
}
}
for (int i = 0; i < c.Controls.Count; ++i)
clone.Controls.Add(CloneControl(c.Controls[i]));
return clone;
}
try this for page includes LiteralControl and ResourceBasedLiteralControl.
public static System.Web.UI.Control CloneControl(Control c)
{
Type type = c.GetType();
var clone = (0 == type.GetConstructors().Length) ? new Control() : Activator.CreateInstance(type) as Control;
if (c is HtmlControl)
{
clone.ID = c.ID;
AttributeCollection attributeCollection = ((HtmlControl)c).Attributes;
System.Collections.ICollection keys = attributeCollection.Keys;
foreach (string key in keys)
{
((HtmlControl)c).Attributes.Add(key, (string)attributeCollection[key]);
}
}else if(c is System.Web.UI.LiteralControl){
clone = new System.Web.UI.LiteralControl(((System.Web.UI.LiteralControl)(c)).Text);
}
else
{
PropertyInfo[] properties = c.GetType().GetProperties();
foreach (PropertyInfo p in properties)
{
// "InnerHtml/Text" are generated on the fly, so skip them. "Page" can be ignored, because it will be set when control is added to a Page.
if (p.Name != "InnerHtml" && p.Name != "InnerText" && p.Name != "Page" && p.CanRead && p.CanWrite)
{
try
{
ParameterInfo[] indexParameters = p.GetIndexParameters();
p.SetValue(clone, p.GetValue(c, indexParameters), indexParameters);
}
catch
{
}
}
}
}
int cControlsCount = c.Controls.Count;
for (int i = 0; i < cControlsCount; ++i)
{
clone.Controls.Add(CloneControl(c.Controls[i]));
}
return clone;
}
ref:
Dynamic Web Controls, Postbacks, and View State
By Scott Mitchell
Upvoted answers didn't work for me. Here's my solution to creating a copy of an ASP.NET control object.
// Copies the custom properties on the control. To use it, cast your object to it.
public static object CopyUserControl(object obj, object obj2)
{
Type type = obj.GetType();
PropertyInfo[] properties = type.GetProperties();
Type type2 = obj2.GetType();
PropertyInfo[] properties2 = type.GetProperties();
foreach (PropertyInfo property in properties)
{
foreach (PropertyInfo property2 in properties2)
{
// Match the loops and ensure it's not a non-custom property
if (property.Name == property2.Name && property.SetMethod != null && !property.SetMethod.IsSecuritySafeCritical)
property2.SetValue(obj2, property.GetValue(obj, null));
}
}
return obj2;
}
It's useful when you have to copy an ASCX control on the same page with a different html id. Usage:
// Copying myAscxControl1 onto myAscxControl2
myAscxControl2 = (ObjectNameToBeCasted)CopyUserControl(myAscxControl1, myAscxControl2);
It should work with different typed objects too. Property names that you want to copy must match though.

Categories