Compare property values with Reflection - c#

I am trying to build the most budget of ORMs.
I have 2 objects, one "item" and one "original", when creating item, i simply save a COPY of it, in a variable called original. The copy is created via an extension i stole from this post https://stackoverflow.com/a/11308879/10647851
So i want to build my update query, very simple i thought.
foreach (PropertyInfo prop in properties) {
var one = prop.GetValue(original, null);
var typeOne = one.GetType();
var two = prop.GetValue(item, null);
var typeTwo = two.GetType();
if(one != two) { ... }
}
However, one is unequal to two in several cases where you would not expect it.
The ID (type in the model = int?) 2703 is unequal to 2703. I stepped through the code and the type is Int32. Booleans are problems too. Strings work. DateTime?'s work.

Use
if(!object.Equals(one, two)) { ... }
for equality comparison instead. That will invoke the default equality comparator or the overridden one for those types that have implemented it (which includes all value types like int on your example).
!= uses reference equality and therefore produces the same value when the referenced types are exactly the same instance. Using != will give you unexpected results due to the fact you are using GetValue which returns an object.
public object GetValue(object obj)
because one and two are actually reference types (i.e. they are boxed ints instead of value types) the "==" comparator performs a reference equality check.

Related

Cast property values back to original types for comparison with reflection

I'm currently trying to compare to data contracts using reflection.
The data contract represents a configuration for a virtual machine. They are both the same class type. I'm trying to log every change in the configuration when setting a new config. However, the data contracts have a large number of members. It'd be rather tedious to do it manually and it does not accommodate new data members being added later.
I tried using reflection to do it. I've managed to get the values of the data members, but depending on the actual type, comparing them will vary.
Some of the members being compared are :
IEnumerable<Subnets>
IEnumerable<IP Address>
X509Certificates
Enums
and a few more
I'm trying to find a way to cast the property to it's actual class, but everything I've found thus far has failed to work.
Here is an example of what I've done so far with reflection.
ExampleClass firstExample = new ExampleClass("a", 1);
ExampleClass secondExample = new ExampleClass("a", 2);
List<string> exampleList = new List<string>()
{
"a",
"b"
};
firstExample.ValueThree = exampleList;
secondExample.ValueThree = exampleList;
firstExample.FourthValue = new List<string>()
{
"c",
"d"
};
secondExample.FourthValue = new List<string>()
{
"c",
"d"
};
Type exampleType = firstExample.GetType();
foreach (PropertyInfo item in exampleType.GetProperties())
{
var firstExampleValue = item.GetValue(firstExample);
var secondExampleValue = item.GetValue(secondExample);
string propName = item.Name;
bool areEqual = firstExampleValue.Equals(secondExampleValue);
Console.WriteLine(propName);
Console.WriteLine(string.Format("\nFirst Value : {0}\nSecond Value : {1}", firstExampleValue, secondExampleValue));
Console.WriteLine(string.Format("Values are equal : {0}\n\n", areEqual));
}
Is there a practical way to convert these to there actual classes as defined by the data contract? I've looked into Convert.ChangeType but found that this will not work for a good portion of the members as they are not IConvertable.
Thanks,
Edwin
This isn't about getting the underlying type. This is about what is considered "equal".
In .NET, every type inherits from object and thus inherits Object.Equals(). But for reference types Object.Equals() will only compare whether the two objects are the exact same object, not whether their contents are equal.
For example, new List<string>() == new List<string>() will always be false.
That's why firstExample == secondExample and firstExampleValue.Equals(secondExampleValue) will always be false.
However, classes can override the Equals method. For example, String overrides Equals() so that it compares the contents of both.
In this case, if ExampleClass is a class that you control, you can override Equals and tell it to compare the contents of each property (and the contents of each List) to decide if they're equal. Once you do that, then you can just write firstExample == secondExample and it would be true.
The documentation of Object.Equals() actually has a simple example of that: https://learn.microsoft.com/en-us/dotnet/api/system.object.equals
Update: Casting to the underlying type doesn't give you any advantage. Even if a type is cast to object, it still uses Equals from the underlying type when you compare. For example, this results in true: ((object)"hello") == ((object)"hello") because it uses String.Equals not Object.Equals.
So your real problem is knowing whether the type has overridden Equals. You can test if a type has overridden Equals like this:
var type = typeof(string);
var overriddenEquals = type.GetMember("Equals").First().DeclaringType != typeof(object);
But that still doesn't help you for types that haven't overridden equals. How do you compare them?
The only way to do this is to be aware of the types that will be used and test for those specifically (a bunch of if statements) and decide how you will compare them. (for lists, you can test for is IEnumerable, loop through it, then test the type inside the list)
And in those cases, it doesn't matter if the type implements IConvertible since you will know what the type is and can cast directly to it, like:
if (obj is Subnets objSubnets) {
//compare objSubnets to another Subnets object
}

What check CollectionAssert.AreEquivalent

I was reading about the method CollectionAssert.AreEquivalent in a MSDN article and according to MSDN:
Two collections are equivalent if they have the same elements in the same quantity, but in any order. Elements are equal if their values are equal, not if they refer to the same object.
I tried the following code in Visual Studio:
var first = new TradeData { ID = "A", MarketPrice = 0 };
var mockFir = new TradeData { ID = "A", MarketPrice = 0 };
var collection = new List<TradeData> { first };
var mockCollection = new List<TradeData> { mockFir };
CollectionAssert.AreEquivalent(collection, mockCollection);
But I got an Exception:
CollectionAssert.AreEquivalent failed
So, my question is: what exactly MSDN mean when they say that "Elements are equal if their values are equal, not if they refer to the same object"?
Because the TradeData class does not override object.Equals, the base implementation takes over, which compares two objects by reference. Although first and mockFir contain the same values they are not the same object and so they are not considered equal. If you override Equals in the TradeData class your example will work.
"Elements are equal if their values are equal, not if they refer to the same object"
It means that CollectionAssert.AreEquivalent uses Object.equals and not Object.ReferenceEquals. Depending on the Object type and implementation of Object.Equals, values will (rather "can") be used for comparison.
Object.Equals has the following default behaviour for the two Object types (paraphrasing):
Value types are equal if they are of the same type and their public and private fields have the same value.
Reference types are equal when they are the same object. For reference types a call to Equals is equivalent to a call to ReferenceEquals. Reference equality means that the object variables refer to the same object.
The assertion failed in the above code sample because you created different objects to insert in both Lists. AreEquivalent compares objects using Object.Equals method. The default implementation of equals method will return true for REference type objects only if pointing to the same object. To compare the actual value of these objects, you will need to override the equals method for TradeData.
It is a bummer that there is an alternate way to use comparator for CollectionAssert.AreEqual, but not so for CollectionAssert.AreEquivalent.
https://msdn.microsoft.com/en-us/library/ms243753.aspx

How to check if deserealized class members have value?

I have an initial snippet which deserealized parameters, checks for value and handles the error:
var param = js.Deserialize<int?>(jqData.Params);
if (param.HasValue)
{
resp.ReturnValue = param.Value;
}
else
{
//handle error code
}
Now, during my modification, I have changed the method to accept a list of class parameters instead of nullable integers
var param = js.Deserialize<ClassName>(jqData.Params);
Now it invalidates .HasValue and .Value methods.
My question is: How do I properly modify these two lines so it would hold the same meaning as initial if statement?
Thus far I only thought about switching to if (param != null), but I cannot think of proper equivalent to .Value.
As soon as ClassName is a class (see - a reference type) you just need to check if it is not null.
If it is not - then the variable holds a reference to an object that you use as-is.

Comparing the value of a property in two instances

I think I'm having boxing issues
foreach(var p in item.GetType().GetProperties().
Where(p => p.GetValue(original, null) is ValueType))
{
var originalValue = p.GetValue(original, null);
var modifiedValue = p.GetValue(item, null);
if (!originalValue.Equals(modifiedValue))
kvpData.AppendFormat("{0}={1}&", p.Name, originalValue);
}
originalValue is never equal to modifiedValue, Im guessing it's because they are boxed inside Object. But how do I fix it?
It is not a boxing issue. Equals is a virtual method, which the boxed value types override just fine.
However, I am not sure what the issue is. Could it be that there aren’t actually any matching properties? Remember that GetProperties() without any parameters will only return public properties. If the properties you need are private, you need to add some BindingFlags:
GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
(I’m assuming here that you don’t want static properties.)
Are you also sure that it is actually properties that you are after and not fields? Remember that if you declare something as
public string Name;
then it’s a field, whereas
public string Name { get; set; }
is a property. If it’s actually fields you need, you need to use GetFields() instead of GetProperties() with the same binding flags.
Linq is a great tool, but I'm not sure why you're using it here. You're actually causing the set of properties to get iterated twice, which is very expensive. I'd write the code without Linq. Also, there's no need to get the value more than once, which is again very expensive. Give this code a try. It avoids the flaws I pointed out and was comparing correctly when I made and tested a dummy class with it:
foreach(PropertyInfo p in item.GetType().GetProperties())
{
if (p.PropertyType.BaseType == typeof(ValueType) || p.PropertyType == typeof(string))
{
var originalValue = p.GetValue(original, null);
var modifiedValue = p.GetValue(item, null);
if (originalValue != modifiedValue) kvpData.AppendFormat("{0}={1}&", p.Name, originalValue);
}
}
Also, note that strings are not a ValueType although they do implement a value comparison.
From MSDN: Object.Equals :
The default implementation of Equals supports reference equality for
reference types, and bitwise equality for value types. Reference
equality means the object references that are compared refer to the
same object. Bitwise equality means the objects that are compared have
the same binary representation.
That means that in your case this 2 objects (if they are reference types) are never point to the same instance.
There is no an easy way to resolve this in so generic way as you would like to do.
But you can implement (just an example) an IComparable on those types you gonna to compare, and after in this iteration check if the type of the value rturned implements that interface, so cast and call it's implemented IComparable.CompareTo method.
You can check if the object implements specified interfacce, in this current case IComparable, you can do something like this:
originalValue .GetType().GetInterfaces().Any(x =>
x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IComparable))
Hope this helps.

Property / field initializers in code generation

I'm generating code in a visual studio extension using CodeDom and plain code strings. My extension reads a current classes declared fields and properties using reflection and generates contructors, initializers, implements certain interfaces, etc.
The generator class is simple:
public class CodeGenerator < T >
{
public string GetCode ()
{
string code = "";
T type = typeof(T);
List < PropertyInfo > properties = t.GetProperties();
foreach (PropertyInfo property in properties)
code += "this." + property.Name + " = default(" + property.PropertyType.Name + ")";
}
}
I'm stuck at field and property initializers in two ways.
Firstly, although default(AnyNonGenericValueOrReferenceType) seems to work in most cases, I'm uncomfortable with using it in generated code.
Secondly, it does not work for generic types since I can't find a way to get the underlying type of the generic type. So if a property is List < int >, property.PropertyType.Name returns List`1. There are two problems here. First, I need to get the proper name for the generic type without using string manipulation. Second, I need to access the underlying type. The full property type name returns something like:
System.Collections.Generic.List`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
Before I try to answer, I feel compelled to point out that what you're doing seems redundant. Assuming that you are putting this code into a constructor, generating something like:
public class Foo
{
private int a;
private bool b;
private SomeType c;
public Foo()
{
this.a = default(int);
this.b = default(bool);
this.c = default(SomeType);
}
}
is unnecessary. That already happens automatically when a class is constructed. (In fact, some quick testing shows that these assignments aren't even optimized away if they're done explicitly in the constructor, though I suppose the JITter could take care of that.)
Second, the default keyword was designed in large part to do exactly what you're doing: to provide a way to assign the "default" value to a variable whose type is unknown at compile time. It was introduced for use by generic code, I assume, but auto-generated code is certainly correct in using it as well.
Keep in mind that the default value of a reference type is null, so
this.list = default(List<int>);
does not construct a new List<int>, it just sets this.list to null. What I suspect you want to do, instead, is to use the Type.IsValueType property to leave value types at their default values, and initialize reference types using new.
Lastly, I think what you're looking for here is the IsGenericType property of the Type class and the corresponding GetGenericArguments() method:
foreach (PropertyInfo property in properties)
{
if (property.Type.IsGenericType)
{
var subtypes = property.Type.GetGenericArguments();
// construct full type name from type and subtypes.
}
else
{
code += "this." + property.Name + " = default(" + property.PropertyType.Name + ")";
}
}
EDIT:
As far as constructing something useful for a reference type, a common technique I've seen used by generated code is to require a parameterless constructor for any class that you expect to use. It's easy enough to see if a class has a parameterless constructor, by calling Type.GetConstructor(), passing in an empty Type[] (e.g. Type.EmptyTypes), and see if it returns a ConstructorInfo or null. Once that has been established, simply replacing default(typename) with new typename() should achieve what you need.
More generally you can supply any array of types to that method to see if there's a matching constructor, or call GetConstructors() to get them all. Things to look out for here are the IsPublic, IsStatic, and IsGenericMethod fields of the ConstructorInfo, to find one you can actually call from wherever this code is being generated.
The problem you are trying to solve, though, is going to become arbitrarily complex unless you can place some constraints on it. One option would be to find an arbitrary constructor and build a call that looks like this:
var line = "this." + fieldName + " = new(";
foreach ( var param in constructor.GetParameters() )
{
line += "default(" + param.ParameterType.Name + "),";
}
line = line.TrimEnd(',') + ");"
(Note this is for illustrative purposes only, I'd probably use CodeDOM here, or at least a StringBuilder :)
But of course, now you have the problem of determining the appropriate type name for each parameter, which themselves could be generics. And the reference type parameters would all be initialized to null. And there's no way of knowing which of the arbitrarily many constructors you can pick from actually produces a usable object (some of them may do bad things, like assume you're going to set properties or call methods immediately after you construct an instance.)
How you go about solving those issues is not a technical one: you can recursively apply this same logic to each parameter as far down as you're willing to go. It's a matter of deciding, for your use case, how complex you need to be and what kind of limits you're willing to place on the users.
If you are sure you want to use strings, you will have to write your own method to format those type names. Something like:
static string FormatType(Type t)
{
string result = t.Name;
if (t.IsGenericType)
{
result = string.Format("{0}<{1}>",
result.Split('`')[0],
string.Join(",", t.GetGenericArguments().Select(FormatType)));
}
return result;
}
This code assumes you have all necessary usings in your file.
But I think it's much better to actually use CodeDOM's object model. This way, you don't have to worry about usings, formatting types or typos:
var statement =
new CodeAssignStatement(
new CodePropertyReferenceExpression(new CodeThisReferenceExpression(), property.Name),
new CodeDefaultValueExpression(new CodeTypeReference(property.PropertyType)));
And if you really don't want to use default(T), you can find out whether the type is a reference or value type. If it's a reference type, use null. If it's value type, the default constructor has to exist, and so you can call that.

Categories