Compare types if one is nullable - c#

I need to check if two types are the same:
private bool AreOfTheSameType(object obj1, object obj2) {
return obj1.GetType()==obj2.GetType();
}
This works fine with this values:
var test1=AreOfTheSameType(new DateTime(), new DateTime()) // true;
var test2=AreOfTheSameType(new int(), new DateTime()) // false;
What I now want is that the following returns true, too:
var test3=AreOfTheSameType(new int?(), new int())
So if the types have the same base, but one is nullable, the other isn't, I also want to return it as true. Or to say it in another way I want to have a function that returns whether I can store obj1 into obj2 directly using reflection without having to cast the value.
UPDATE
I reduced my code to make it more readable. Looks like this time that was contra-productive. The real-world-code follows:
var entity = Activator.CreateInstance(typeof(T));
Type entityType = typeof(T);
PropertyInfo[] entityProperties = entityType.GetProperties();
foreach (KeyValuePair<string, object> fieldValue in item.FieldValues)
{
if (fieldValue.Value == null) continue;
var property = entityProperties.FirstOrDefault(prop => prop.Name == fieldValue.Key);
if (property != null && property.CanWrite)
{
Type valueType = fieldValue.Value.GetType();
if (fieldValue.Value.GetType() == property.PropertyType) {
// Assign
}
}
}
The problem on the "//Assign" - line is, I have the following two types:
fieldValue.Value.GetType().ToString()="System.DateTime"
property.PropertyType.ToString()="System.Nullable`1[System.DateTime]"
which are obiously not the same but could be assigned

which are obiously not the same but could be assigned
It looks like you're after the Type.IsAssignableFrom method:
var dt = typeof(DateTime);
var nd = typeof(DateTime?);
Console.WriteLine(dt.IsAssignableFrom(nd)); // false
Console.WriteLine(nd.IsAssignableFrom(dt)); //true
Live example: http://rextester.com/KDOW15238

Calling GetType on nullable types returns the original type:
int? i = 5;
Type t = i.GetType();
Console.WriteLine(t.FullName); //"System.Int32"
So AreOfTheSameType((int?)5, 5) should return true.
But as soon as you box a Nullable<T>, you either get null (if Nullable<T>.HasValue was false), or you get the boxed underlying value, losing the "nullable" part. So the problem you're facing here is that new int? will be boxed into a null object when passed to the method.

The problem with nullable types is that empty values box to null, and once they're null, you cannot find out what they were. The only way to solve this, then, is with generics:
private bool AreOfTheSameType<T1,T2>(T1 obj1, T2 obj2) {
// get the types as best we can determine
var t1 = obj1?.GetType() ?? typeof(T1);
var t2 = obj2?.GetType() ?? typeof(T2);
// Nullable<T> => T
t1 = Nullable.GetUnderlyingType(t1) ?? t1;
t2 = Nullable.GetUnderlyingType(t2) ?? t2;
// compare
return t1 == t2;
}
This will use the object if available (to allow for subclasses etc), but will fall back to typeof if the object is null - which means it should work for int? etc... as long as the type of the expression being passed in wasn't object to begin with; if it was object and the value is null, then... you're out of luck - you can't ever find out the original type. By which I mean:
This should be fine:
var test3=AreOfTheSameType(new int?(), new int());
But this will fail:
object x = new int?(), y = new int();
var test4=AreOfTheSameType(x, y);

Related

IsEnum does not recognise enum

My code is .Net 4.0 and I am trying to understand some legacy code I now work with. I can't change it at the moment and i'm sure this code has worked before my time. It needs to make an enum from strings, but the type is not recognized as an enum.
EDIT
I now realize the enum property is actually nullable. So it is a NetType? How can I convert that into an enum if it has a value?
When I debug and see the type that is being checked on the enum, this is what I see:
FullName = System.Nullable1[[AppName.Model.NetType, AppName.Model,
Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]]
Name = Nullable1
This is the enum:
public enum NetType
{
ChoiceOne = 1,
ChoiceTwo = 2
}
Main code, simplified for clarity:
var property = typeof(MainClass).GetProperty("NetType");
var value = GetValue(property.PropertyType, "ChoiceOne");
private object GetValue(Type type, string valueString)
{
if (type.IsEnum)// Why false?
return Enum.Parse(type, valueString);
if (type == typeof (Int32))
return Int32.Parse(valueString);
return valueString;
}
Another option is to use Nullable.GetUnderlyingType() to check to see if it's a nullable enum first. If it is, use the underlying enum; if it's not, do your checks as you normally did.
private object GetValue(Type type, string valueString)
{
// If Nullable.GetUnderlyingType() returns null, it's not nullable
// and you can default to type.
var enumCandidiate = Nullable.GetUnderlyingType(type) ?? type;
if (enumCandidiate.IsEnum)
return Enum.Parse(enumCandidiate, valueString);
if (type == typeof (Int32))
return Int32.Parse(valueString);
return valueString;
}
Here was my test script (using RoslynPad, hence the Dump() calls).
var val = GetValue(typeof(NetType), "ChoiceOne");
val.Dump();
val = GetValue(typeof(NetType?), "ChoiceOne");
val.Dump();
Try this:
var type = property.PropertyType;
object value;
if (type.IsEnum)
{
value = GetValue(type, "ChoiceOne");
}
else if (type.IsGenericType &&
type.GetGenericTypeDefinition() == typeof(Nullable<>) &&
type.GetGenericArguments()[0].IsEnum)
{
value = GetValue(type.GetGenericArguments()[0], "ChoiceOne");
}
It should work for both NetType and NetType?.

Why ExpandoObject doesn't work properly with Guid converted to string?

I have a piece of code that works properly if you pass two strings. For some reason it doesn't work the same if you pass GUID converted to string.
In more details, if I create a new ExpandoObject and pass string value it works but if I pass GUID converted to string it doesn't.
The code below should compare two parameters. In my example I pass the same two strings. With Equal operator it should return true if the strings are the same. If second parameter GUID converted to string it returns false even strings are the same. dynamicObj.Add(memberName, Guid.Parse(value).ToString());
Not sure what I'm missing. Here is my code.
string value = "642188c7-8e10-e111-961b-0ee1388ccc3b";
string memberName = "State";
string contactValue = value;
var dynamicObj = (IDictionary<string, object>)new ExpandoObject(); dynamicObj.Add(memberName, Guid.Parse(value).ToString());
var expression = Expression.Parameter(typeof(object), "arg");
var binder = Binder.GetMember(CSharpBinderFlags.None, memberName, null, new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) });
var property = Expression.Dynamic(binder, typeof(object), expression);
var isValid = false;
var right = Expression.Constant(contactValue);
var result = Expression.MakeBinary(ExpressionType.Equal, property, right);
var func = typeof(Func<,>).MakeGenericType(dynamicObj.GetType(), typeof(bool));
var expr = Expression.Lambda(func, result, expression).Compile();
isValid = (bool)expr.DynamicInvoke(dynamicObj);
The GUID parsing will end up with the same string (value) as just using a string literal.
The difference however is the way it is stored in the dictionary: it's of type Dictionary<string, object>. This means that the Object class its == operator will be used which does a reference equality check. The String class, however, overloads this by doing a value equality check.
That's why this returns true:
Console.WriteLine(value == Guid.Parse(value).ToString());
While this returns false:
Console.WriteLine((object) value == (object) Guid.Parse(value).ToString());
Since strings are immutable, Guid.Parse(value).ToString() will create a new string object and do a reference equality check compared with contactValue (which is the same as value). This will evidently return false compared to using value all along which returns true because you never create a new object.
In order to make it work you can just cast the dynamic operand to a string so it will use the correct overload:
var castDyn = Expression.Convert(property, typeof(string));
var result = Expression.MakeBinary(ExpressionType.Equal, castDyn, right);

Comparing two equal vars return false

Some background to understand the code. I have an MVC application, all my models implement IModel. IModel just enforces to have an int Id property.
The following method "updates" an instances of a model with the data available in a viewmodel. For each property of the viewmodel it checks if a corresponding property exists in the model, if it does It updates the value in the model with those of the viewmodel, if the values are different.
At the last point it goes wrong. The statement : OldValue != NewValue always returns true, even if f.e. both are integers, 1. Why ?
public static Boolean UpdateIfChanged<M, VM>(this M Model, VM ViewModel) where M : IModel
{
Boolean HasUpdates = false;
Type Mtype = typeof(M);
PropertyInfo[] MProperties = Mtype.GetProperties(BindingFlags.Public | BindingFlags.Instance);
Type VMtype = typeof(VM);
PropertyInfo[] VMProperties = VMtype.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var VMProperty in VMProperties)
{
if (!VMProperty.PropertyType.GetInterfaces().Any(x => x.Name == typeof(IModel).Name)
&& MProperties.Any(x => x.Name == VMProperty.Name)
&& Mtype.GetProperty(VMProperty.Name).PropertyType == VMProperty.PropertyType )
{
var OldValue = Mtype.GetProperty(VMProperty.Name).GetValue(Model);
var NewValue = VMtype.GetProperty(VMProperty.Name).GetValue(ViewModel);
if (NewValue != null)
{
if (OldValue == null)
{
Mtype.GetProperty(VMProperty.Name).SetValue(Model, NewValue);
HasUpdates = true;
}
else
{
if (OldValue != NewValue)
{
Mtype.GetProperty(VMProperty.Name).SetValue(Model, NewValue);
HasUpdates = true;
}
}
}
}
}
return HasUpdates;
}
The problem here is that OldValue and NewValue are objects at compile time, not int, and therefore the ==/!= operators call the ones defined by the object class, because all operators are static. The object operators check for referential equality, not logical equality, and thus only check if the two arguments are exactly the same object (the same pointer in C++)
To get around this, you have several options
As Tigran mentioned, type cast the values coming out, so you end up using the operator defined in int instead of object
Call object.Equals(OldValue, NewValue), which checks for null and then calls the virtual Object.Equals(object o), which thus will call the function defined in the actual class/struct of the calling object (in this case int)
your GetValue(..) call returns boxed integer object, so reference type.
Hence your code:
//OldValue and NewValue are Object types and NOT integers !
if (OldValue != NewValue)
compares references and not values.
You didn't notice that as you are using var keyword, which "hides" concrete type.
To correctly overcome this issue, may do like:
....
var OldValue = (int)Mtype.GetProperty(VMProperty.Name).GetValue(Model); //cast to int
var NewValue = (int)VMtype.GetProperty(VMProperty.Name).GetValue(ViewModel);//cast to int
....
Try this if (OldValue.ToString() != NewValue.ToString()). Or if OldValue/NewValue are int-s: if ((int)OldValue != (int)NewValue)
A better syntax would be:
if (!Equals(OldValue, NewValue))

get default value for a type

I'm using a generic DataTable to List conversion method which I found on this site:
private List<T> ConvertToList<T>(DataTable dt)
{
var columnNames = dt.Columns.Cast<DataColumn>()
.Select(c => c.ColumnName)
.ToList();
var properties = typeof(T).GetProperties();
return dt.AsEnumerable().Select(row =>
{
var objT = Activator.CreateInstance<T>();
foreach (var pro in properties)
{
if (columnNames.Contains(pro.Name))
{
pro.SetValue(objT, row[pro.Name], null);
}
}
return objT;
}).ToList();
}
it works great, except when I hit null values for integer fields in pro.SetValue. The reason it fails is because pro.SetValue will set a default value for a type if second parameter is null like this: pro.SetValue(objT, null,null).
but my row[IdColumn] return empty object {} instead of null.
The error I'm getting is:
Object of type 'System.DBNull' cannot be converted to type
'System.Nullable`1[System.Int32]'.
when using int?
and
Object of type 'System.DBNull' cannot be converted to type
[System.Int32]'.
when using int
How do I handle this case?
check for null first:
pro.SetValue(objT, row.IsNull(pro.Name) ? null : row[pro.Name], null);
You probably need to check for DBNull explicitly
if (columnNames.Contains(pro.Name))
{
if (row.IsNull(pro.Name))
pro.SetValue(objT, null, null);
else
pro.SetValue(objT, row[pro.Name], null);
}
This will handle int?, but trying to convert null to an Int32 (non-nullable) should be an error case.

Extension Method for copying properties form object to another, with first attempt

I'm trying to write an extension method that I can use to copy values from one object property to another object of a different type, as long as the property names and types match exactly.
This is what I have:
public static T CopyFrom<T>(this T toObject, object fromObject)
{
var fromObjectType = fromObject.GetType();
var fromProperties = fromObjectType.GetProperties();
foreach (PropertyInfo toProperty in toObject.GetType().GetProperties())
{
PropertyInfo fromProperty = fromObjectType.GetProperty(toProperty.Name);
if (fromProperty != null) // match found
{
// check types
var fromType = fromProperty.PropertyType.UnderlyingSystemType;
var toType = toProperty.PropertyType.UnderlyingSystemType;
if (toType.IsAssignableFrom(fromType))
{
toProperty.SetValue(toObject, fromProperty.GetValue(fromObject, null), null);
}
}
}
return toObject;
}
This is working great for non boxed types, but Nullable<T> returns false when I call
toType.IsAssignableFrom(fromType)
because its type is Nullable<T> and is not the underlying type T.
I read here that GetType() should unbox the Nullable<T> so it returns T but if I call that on PropertyInfo.PropertyType I get ReflectedMemberInfo and not the type T im looking for.
I think I'm missing something obvious here, so I thought I would throw it open to SO to get some advice.
Anyone have any ideas?
UPDATE: Here is the final method for anyone searching for this.
public static T CopyFrom<T>(this T toObject, object fromObject)
{
var fromObjectType = fromObject.GetType();
foreach (PropertyInfo toProperty in toObject.GetType().GetProperties())
{
PropertyInfo fromProperty = fromObjectType.GetProperty(toProperty.Name);
if (fromProperty != null) // match found
{
// check types
var fromType = Nullable.GetUnderlyingType(fromProperty.PropertyType) ?? fromProperty.PropertyType;
var toType = Nullable.GetUnderlyingType(toProperty.PropertyType) ?? toProperty.PropertyType;
if (toType.IsAssignableFrom(fromType))
{
toProperty.SetValue(toObject, fromProperty.GetValue(fromObject, null), null);
}
}
}
return toObject;
}
You're looking for Nullable.GetUnderlyingType.
For example:
toType = Nullable.GetUnderlyingType(toType) ?? toType;

Categories

Resources