Related
I have a need to update object A's property if null with that from object B's equivalent property if that is not null. I wanted code I can use for various objects.
I had a version working until one of the objects contained a propert of type List, which is where I have a blank in the code below. My main question is how can I best implement this part of the code. Secondly is there a better way of doing this whole thing and thirdly I know its never going to be rapid but any suggestions to speed it up would be appreciated.
Thanks in advance.
public T MergeWith<T, U>(T primarySource, U secondarySource) where U : class, T
{
Type primaryType = typeof(T);
Type secondaryType = typeof(U);
foreach (PropertyInfo primaryInfo in primaryType.GetProperties())
{
if (primaryInfo.CanWrite)
{
object currentPrimary = primaryInfo.GetValue(primarySource, null);
PropertyInfo secondaryInfo = secondaryType.GetProperty(primaryInfo.Name);
object currentSecondary = secondaryInfo.GetValue(secondarySource, null);
if (currentPrimary == null && currentSecondary != null)
{
primaryInfo.SetValue(primarySource, currentSecondary, null);
}
else if ((currentPrimary != null && currentSecondary != null) && isChildClass(primaryInfo))
{
if (isCollection(currentPrimary))
{
// here
}
else
{
MethodInfo method = typeof(NavigationModel).GetMethod("MergeWith");
MethodInfo generic = method.MakeGenericMethod(primaryInfo.PropertyType, primaryInfo.PropertyType);
object returnChild = generic.Invoke(this, new object[2] { currentPrimary, currentSecondary });
}
}
}
}
return primarySource;
}
private bool isCollection(object property)
{
return typeof(ICollection).IsAssignableFrom(property.GetType())
|| typeof(ICollection<>).IsAssignableFrom(property.GetType());
}
private bool isChildClass(PropertyInfo propertyInfo)
{
return (propertyInfo.PropertyType.IsClass && !propertyInfo.PropertyType.IsValueType &&
!propertyInfo.PropertyType.IsPrimitive && propertyInfo.PropertyType.FullName != "System.String");
}
I have created the below extension method for use in my latest project and it works fine, collections and all. It is a pretty much a simpler version of what you are doing in your method. With mine both classes have to be the same type. What problem do you encounter with collections?
public static class ExtensionMethods
{
public static TEntity CopyTo<TEntity>(this TEntity OriginalEntity, TEntity NewEntity)
{
PropertyInfo[] oProperties = OriginalEntity.GetType().GetProperties();
foreach (PropertyInfo CurrentProperty in oProperties.Where(p => p.CanWrite))
{
if (CurrentProperty.GetValue(NewEntity, null) != null)
{
CurrentProperty.SetValue(OriginalEntity, CurrentProperty.GetValue(NewEntity, null), null);
}
}
return OriginalEntity;
}
}
Hi I modified Ben Robinsons solution in order to not overwrite Collections or list, instead, it adds the elements of one object to the other one where the merging is happening:
public static class ExtensionMethods
{
public static TEntity CopyTo<TEntity>(this TEntity OriginalEntity, TEntity EntityToMergeOn)
{
PropertyInfo[] oProperties = OriginalEntity.GetType().GetProperties();
foreach (PropertyInfo CurrentProperty in oProperties.Where(p => p.CanWrite))
{
var originalValue = CurrentProperty.GetValue(EntityToMergeOn);
if (originalValue != null)
{
IListLogic<TEntity>(OriginalEntity, CurrentProperty, originalValue);
}
else
{
var value = CurrentProperty.GetValue(OriginalEntity, null);
CurrentProperty.SetValue(EntityToMergeOn, value, null);
}
}
return OriginalEntity;
}
private static void IListLogic<TEntity>(TEntity OriginalEntity, PropertyInfo CurrentProperty, object originalValue)
{
if (originalValue is IList)
{
var tempList = (originalValue as IList);
var existingList = CurrentProperty.GetValue(OriginalEntity) as IList;
foreach (var item in tempList)
{
existingList.Add(item);
}
}
}
}
When I run the following code, it only returns a MethodInfo/FieldInfo/etc. that belongs directly to the Type that I'm searching for the info object in. How do I find the info object regardless of the fact that it resides in a base class and could be private?
obj.GetType().GetMethod(methodName, bindingFlags);
Well, you answered your own question, but as far as I understood, you main requirement is How do I find the info object regardless of where it is found in the hierarchy?
You don't need recursion here to get all members in the full hierarchy. You can use GetMembers function on a Type and it will return all members including all base classes.
Next code example demonstrates this:
var names =
typeof(MyClass).GetMembers()
.Select (x => x.Name);
Console.WriteLine (string.Join(Environment.NewLine, names));
for such structure
class MyClass : Base
{
public string Name { get; set; }
public string Surname { get; set; }
}
class Base
{
public string Name { get; set; }
}
returns
get_Name
set_Name
get_Surname
set_Surname
get_Name
set_Name
ToString
Equals
GetHashCode
GetType
.ctor
Name
Surname
note that get_Name accessor for auto-property appears twice because MyClass hides Name property of the base class. Also note ToString, GetType and other methods, defined in object class
The following code will look through each base class of an object if the info object is not found in the child class. Note that though it will return the base class info object, it will return the object that it "runs into first", so if you have a variable called _blah in your child class and a variable called _blah in a base class, then the _blah from the child class will be returned.
public static MethodInfo GetMethodInfo(this Type objType, string methodName, BindingFlags flags, bool isFirstTypeChecked = true)
{
MethodInfo methodInfo = objType.GetMethod(methodName, flags);
if (methodInfo == null && objType.BaseType != null)
{
methodInfo = objType.BaseType.GetMethodInfo(methodName, flags, false);
}
if (methodInfo == null && isFirstTypeChecked)
{
throw new MissingMethodException(String.Format("Method {0}.{1} could not be found with the following BindingFlags: {2}", objType.ReflectedType.FullName, methodName, flags.ToString()));
}
return methodInfo;
}
public static FieldInfo GetFieldInfo(this Type objType, string fieldName, BindingFlags flags, bool isFirstTypeChecked = true)
{
FieldInfo fieldInfo = objType.GetField(fieldName, flags);
if (fieldInfo == null && objType.BaseType != null)
{
fieldInfo = objType.BaseType.GetFieldInfo(fieldName, flags, false);
}
if (fieldInfo == null && isFirstTypeChecked)
{
throw new MissingFieldException(String.Format("Field {0}.{1} could not be found with the following BindingFlags: {2}", objType.ReflectedType.FullName, fieldName, flags.ToString()));
}
return fieldInfo;
}
I altered bsara's implementation to be able to find private members.
I use it like this:
public static void Save(string filename, object obj)
{
try
{
using Stream s = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None);
var b = new BinaryFormatter();
b.Serialize(s, obj);
}
catch(SerializationException e)
{
var type= e.Message.Split("in Assembly")[0].Replace("Type", string.Empty).Replace("'", string.Empty).Trim();
var assembly=e.Message.Split("in Assembly")[1].Split("'")[1];
var atype= Type.GetType(type);
string path = FindObject(new Stack<object>(new object[] { obj }), atype, "[myself]");
throw new SerializationException($"Could not serialize path {path} in {obj.GetType().Name} due to not being able to process {type} from {assembly}. see inner exception for details", e);
}
}
the methods with a small update:
private static bool TrySerialize(object obj)
{
if(obj == null)
return true;
var stream = new MemoryStream();
var bf = new BinaryFormatter();
try
{
bf.Serialize(stream, obj);
}
catch(SerializationException)
{
return false;
}
return true;
}
private static string FindObject(Stack<object> self, Type typeToFind, string path)
{
var _self = self.Peek();
if(self.Where(x => x.Equals(_self)).Count() > 1) return null;
foreach(var prop in _self.GetType().GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic).Where(x => !x.GetCustomAttributes(true).Any(y => y is XmlIgnoreAttribute)))
{
switch(prop.MemberType)
{
case System.Reflection.MemberTypes.Property:
{
var line = string.Format("{0}::{1}", path, prop.Name);
var _prop = prop as PropertyInfo;
if(_prop.GetIndexParameters().Count() > 0) break;
if(typeToFind.IsAssignableFrom(_prop.PropertyType))
return line;
if(_prop.PropertyType.IsPrimitive || _prop.PropertyType == typeof(DateTime) || _prop.PropertyType == typeof(string))
continue;
var subInst = _prop.GetValue(_self, new object[0]);
if(subInst == null)
continue;
if(!TrySerialize(subInst))
{
System.Diagnostics.Debugger.Log(0, "", string.Format("Cannot serialize {0}\n", line));
}
self.Push(subInst);
var result = FindObject(self, typeToFind, line);
self.Pop();
if(result != null)
return result;
}
break;
case System.Reflection.MemberTypes.Field:
{
var line = string.Format("{0}::*{1}", path, prop.Name);
var _prop = prop as FieldInfo;
if(typeToFind.IsAssignableFrom(_prop.FieldType))
return line;
if(_prop.FieldType.IsPrimitive || _prop.FieldType == typeof(DateTime) || _prop.FieldType == typeof(string))
continue;
var subInst = _prop.GetValue(_self);
if(subInst == null)
continue;
if(!TrySerialize(subInst))
{
System.Diagnostics.Debugger.Log(0, "", string.Format("Cannot serialize field {0}\n", line));
}
self.Push(subInst);
var result = FindObject(self, typeToFind, line);
self.Pop();
if(result != null)
return result;
}
break;
case System.Reflection.MemberTypes.Event:
{
var line = string.Format("{0}::!{1}", path, prop.Name);
var _prop = prop as EventInfo;
if(typeToFind.IsAssignableFrom(_prop.EventHandlerType))
return line;
var field = _self.GetType().GetField(_prop.Name,
BindingFlags.NonPublic |BindingFlags.Instance |BindingFlags.GetField);
if(field != null && !field.GetCustomAttributes(true).Any(x => x is NonSerializedAttribute) && !TrySerialize(field.GetValue(_self)))
{
System.Diagnostics.Debugger.Log(0, "", string.Format("Cannot serialize event {0}\n", line));
}
}
break;
case System.Reflection.MemberTypes.Custom:
{
}
break;
default: break;
}
}
if(_self is IEnumerable)
{
var list = (_self as IEnumerable).Cast<object>();
var index = 0;
foreach(var item in list)
{
index++;
self.Push(item);
var result = FindObject(self, typeToFind, string.Format("{0}[{1}]", path, index));
self.Pop();
if(result != null)
return result;
}
}
return null;
}
I need to define a method to compare two different objects of a same type. The type of objects is not specific. The objects may be a DLL type, so I can't override Equals method. I have to do this by reflection. This code works if all the members of objects are of primitive type. But it doesn't work when an object has a field that isn't primitive. How can I do it by reflection?
public bool Equals(object obj1, object obj2)
{
List<FieldInfo> fieldInfos = obj1.GetType().GetFields().ToList();
return (fieldInfos.Select(fieldInfo => new {fieldInfo, type = fieldInfo.GetType()})
.Where(#t => #t.type.IsPrimitive || #t.type == typeof(string) || #t.type == typeof(Decimal))
.Select(#t => #t.fieldInfo)).All(fieldInfo => fieldInfo.GetValue(obj1).Equals(fieldInfo.GetValue(obj2)));
}
I have recently been told about this lib that will do exactly what you are wanting
http://comparenetobjects.codeplex.com/releases/view/47978
I want the utility function to compare any 2 objects. All of the type I want to cover is
Primitive Type
Any class that Implement IEnumerable (Like Dict or List)
Any Class
so I use generic and reflection to do so. I code it like this.
public static bool CompareObjects<T>(T expectInput, T actualInput)
{
// If T is primitive type.
if (typeof(T).IsPrimitive)
{
if (expectInput.Equals(actualInput))
{
return true;
}
return false;
}
if (expectInput is IEquatable<T>)
{
if (expectInput.Equals(actualInput))
{
return true;
}
return false;
}
if (expectInput is IComparable)
{
if (((IComparable)expectInput).CompareTo(actualInput) == 0)
{
return true;
}
return false;
}
// If T is implement IEnumerable.
if (expectInput is IEnumerable)
{
var expectEnumerator = ((IEnumerable)expectInput).GetEnumerator();
var actualEnumerator = ((IEnumerable)actualInput).GetEnumerator();
var canGetExpectMember = expectEnumerator.MoveNext();
var canGetActualMember = actualEnumerator.MoveNext();
while (canGetExpectMember && canGetActualMember && true)
{
var currentType = expectEnumerator.Current.GetType();
object isEqual = typeof(Utils).GetMethod("CompareObjects").MakeGenericMethod(currentType).Invoke(null, new object[] { expectEnumerator.Current, actualEnumerator.Current });
if ((bool)isEqual == false)
{
return false;
}
canGetExpectMember = expectEnumerator.MoveNext();
canGetActualMember = actualEnumerator.MoveNext();
}
if (canGetExpectMember != canGetActualMember)
{
return false;
}
return true;
}
// If T is class.
var properties = typeof(T).GetProperties();
foreach (var property in properties)
{
var expectValue = typeof(T).GetProperty(property.Name).GetValue(expectInput);
var actualValue = typeof(T).GetProperty(property.Name).GetValue(actualInput);
if (expectValue == null || actualValue == null)
{
if (expectValue == null && actualValue == null)
{
continue;
}
return false;
}
object isEqual = typeof(Utils).GetMethod("CompareObjects").MakeGenericMethod(property.PropertyType).Invoke(null, new object[] { expectValue, actualValue });
if ((bool)isEqual == false)
{
return false;
}
}
return true;
}
I need to trim some string properties in my objects, but I don't want to go to all objects and properties and in the set properties do the trim method (there is a lot of objects, 300+ and a lot of string properties).
One tip: all my objects have a super class called CoreTransaction, so I can use it (with some kind of reflection) to do this thing more easily.
Is that possible?
var stringProperties = obj.GetType().GetProperties()
.Where(p => p.PropertyType == typeof (string));
foreach (var stringProperty in stringProperties)
{
string currentValue = (string) stringProperty.GetValue(obj, null);
stringProperty.SetValue(obj, currentValue.Trim(), null) ;
}
Thank you to Bala R for your solution to the OP's problem. I converted your solution to an extension method and fixed a problem where null values were throwing errors.
/// <summary>Trim all String properties of the given object</summary>
public static TSelf TrimStringProperties<TSelf>(this TSelf input)
{
var stringProperties = input.GetType().GetProperties()
.Where(p => p.PropertyType == typeof(string) && p.CanWrite);
foreach (var stringProperty in stringProperties)
{
string currentValue = (string)stringProperty.GetValue(input, null);
if (currentValue != null)
stringProperty.SetValue(input, currentValue.Trim(), null);
}
return input;
}
I fixed landi's answer to accommodate child nullable objects and handle IEnumerable collections (loop through a List of object and trim string properties). I made an edit to his answer which was rejected for not being on topic, but that's a load of garbage. Hopefully this helps someone, as landi's answer didn't work on every object type I had. Now it does.
public static class ExtensionMethods
{
public static void TrimAllStrings<TSelf>(this TSelf obj)
{
if(obj != null)
{
if(obj is IEnumerable)
{
foreach(var listItem in obj as IEnumerable)
{
listItem.TrimAllStrings();
}
}
else
{
BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy;
foreach (PropertyInfo p in obj.GetType().GetProperties(flags))
{
Type currentNodeType = p.PropertyType;
if (currentNodeType == typeof (String))
{
string currentValue = (string)p.GetValue(obj, null);
if (currentValue != null)
{
p.SetValue(obj, currentValue.Trim(), null);
}
}
// see http://stackoverflow.com/questions/4444908/detecting-native-objects-with-reflection
else if (currentNodeType != typeof (object) && Type.GetTypeCode(currentNodeType) == TypeCode.Object)
{
p.GetValue(obj, null).TrimAllStrings();
}
}
}
}
}
}
I have written a Extension Method which also takes care of subclasses and strings on referenced classes (like parent.Child.Name)
public static class ExtensionMethods
{
public static void TrimAllStrings<TSelf>(this TSelf obj)
{
BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy;
foreach (PropertyInfo p in obj.GetType().GetProperties(flags))
{
Type currentNodeType = p.PropertyType;
if (currentNodeType == typeof (String))
{
string currentValue = (string)p.GetValue(obj, null);
if (currentValue != null)
{
p.SetValue(obj, currentValue.Trim(), null);
}
}
// see http://stackoverflow.com/questions/4444908/detecting-native-objects-with-reflection
else if (currentNodeType != typeof (object) && Type.GetTypeCode(currentNodeType) == TypeCode.Object)
{
p.GetValue(obj, null).TrimAllStrings();
}
}
}
}
I'm not sure about changing the behaviour of your accessors. That doesn't sound easy at all. How about adding the trimming to your base class?
class CoreTransaction
{
public void Trim()
{
IEnumerable<PropertyInfo> stringProperties =
this.GetType().GetProperties()
.Where(p => p.PropertyType == typeof(string) && p.CanRead && p.CanWrite);
foreach (PropertyInfo property in stringProperties)
{
string value = (string)property.GetValue(this, null);
value = value.Trim();
property.SetValue(this, value, null);
}
}
}
(Also, note the check that your fields can be both read and written to.)
EDIT: You could then add something like this to your base class, and trim all of them in one go.
The WeakReference class will allow you to easily keep track of your instances without getting in the way of the garbage collector:
class CoreTransaction
{
private static List<WeakReference> allCoreTransactions = new List<WeakReference>();
public CoreTransaction()
{
allCoreTransactions.Add(new WeakReference(this));
}
public static void TrimAll()
{
foreach (WeakReference reference in allCoreTransactions)
{
if (reference.IsAlive)
{
((CoreTransaction)reference.Target).Trim();
}
}
}
}
You could use reflection to do something like this:
// o is your instance object
List<PropertyInfo> fields = o.GetType().GetProperties()
.Where(i => i.PropertyType == typeof(string));
fields.ForEach(i => i.SetValue(o, ((string)i.GetValue(o, null)).Trim(), new object[]{}));
Thank landi for his solution. I altered his method to add support for Classes with Index Parameters and also to check if the obj is null before continuing.
public static void TrimAllStrings<TSelf>(this TSelf obj)
{
if (obj == null)
return;
BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy;
foreach (PropertyInfo p in obj.GetType().GetProperties(flags))
{
Type currentNodeType = p.PropertyType;
if (currentNodeType == typeof(String))
{
string currentValue = (string)p.GetValue(obj, null);
if (currentValue != null)
{
p.SetValue(obj, currentValue.Trim(), null);
}
}
// see http://stackoverflow.com/questions/4444908/detecting-native-objects-with-reflection
else if (currentNodeType != typeof(object) && Type.GetTypeCode(currentNodeType) == TypeCode.Object)
{
if (p.GetIndexParameters().Length == 0)
{
p.GetValue(obj, null).TrimAllStrings();
}else
{
p.GetValue(obj, new Object[] { 0 }).TrimAllStrings();
}
}
}
}
Extended Own's solution and added a check that it's possible to write to property. Had some "Property set method not found" errors due to a Uri property.
public static class ExtensionMethods
{
public static void TrimAllStrings<TSelf>(this TSelf obj)
{
if(obj != null)
{
if(obj is IEnumerable)
{
foreach(var listItem in obj as IEnumerable)
{
listItem.TrimAllStrings();
}
}
else
{
BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy;
foreach (PropertyInfo p in obj.GetType().GetProperties(flags))
{
Type currentNodeType = p.PropertyType;
if (currentNodeType == typeof (String))
{
string currentValue = (string)p.GetValue(obj, null);
if (currentValue != null && p.CanWrite)
{
p.SetValue(obj, currentValue.Trim(), null);
}
}
// see http://stackoverflow.com/questions/4444908/detecting-native-objects-with-reflection
else if (currentNodeType != typeof (object) && Type.GetTypeCode(currentNodeType) == TypeCode.Object)
{
p.GetValue(obj, null).TrimAllStrings();
}
}
}
}
}
}
For those who use VB.NET, I converted thrawnis's answer and added a condition to only return those properties that are not ReadOnly. Otherwise if your class has readonly properties you will get runtime error when you try to SetValue for those properties.
''' <summary>
''' Trim all NOT ReadOnly String properties of the given object
''' </summary>
<Extension()>
Public Function TrimStringProperties(Of T)(ByVal input As T) As T
Dim stringProperties = input.GetType().GetProperties().Where(Function(p) p.PropertyType = GetType(String) AndAlso p.CanWrite)
For Each stringProperty In stringProperties
Dim currentValue As String = Convert.ToString(stringProperty.GetValue(input, Nothing))
If currentValue IsNot Nothing Then
stringProperty.SetValue(input, currentValue.Trim(), Nothing)
End If
Next
Return input
End Function
You can try it:
static public class Trim<T>
where T : class
{
static public readonly Action<T> TrimAllStringFields = Trim<T>.CreateTrimAllStringFields();
static private Action<T> CreatTrimAllStringFields()
{
var instance = Expression.Parameter(typeof(T));
return Expression.Lambda<Action<T>>(Expression.Block(instance.Type.GetFields(BindingsFlags.Instance| BindingFlags.NonPublic | BindingFlags.Public).Select(field => Expression.Assign(Expression.Field(instance, field)) as Expression), instance).Compile();
}
}
Use it like this :
var myinstance = new MyClass();
Trim<MyClass>.TrimAllStringFields(myinstance);
I took OwN's answer but made these changes:
used early exit to decrease the nesting of if
used var everywhere, renaming a few variables
added Unit Tests
ObjectExtensions.cs
using System;
using System.Collections;
using System.Reflection;
namespace YourProject.Infrastructure.Extensions
{
public static class ObjectExtensions
{
// Derived from https://stackoverflow.com/a/50193184/
public static void TrimAllStrings<TSelf>(this TSelf obj)
{
if (obj == null)
{
return;
}
if (obj is IEnumerable)
{
foreach (var item in obj as IEnumerable)
{
item.TrimAllStrings();
}
return;
}
var flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy;
foreach (var prop in obj.GetType().GetProperties(flags))
{
var nodeType = prop.PropertyType;
if (nodeType == typeof(String))
{
string currentValue = (string)prop.GetValue(obj, null);
if (currentValue != null)
{
prop.SetValue(obj, currentValue.Trim(), null);
}
}
// see http://stackoverflow.com/questions/4444908/detecting-native-objects-with-reflection
else if (nodeType != typeof(object) && Type.GetTypeCode(nodeType) == TypeCode.Object)
{
prop.GetValue(obj, null).TrimAllStrings();
}
}
}
}
}
ObjectExtensionsTests.cs
using System.Collections.Generic;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using YourProject.Infrastructure.Extensions;
namespace YourProjectTests.Infrastructure.Extensions
{
[TestClass]
public class ObjectExtensionsTests
{
[TestMethod]
public void NullObject_DoesNothing()
{
// Arrange
SomeStringPropertiesClass test = null;
// Act
test.TrimAllStrings();
}
public class NoStringPropertiesClass
{
public int IntProperty { get; set; }
}
[TestMethod]
public void NoStringProperties_DoesNothing()
{
// Arrange
var test = new NoStringPropertiesClass()
{
IntProperty = 42
};
// Act
test.TrimAllStrings();
// Assert
test.IntProperty.Should().Be(42);
}
public class SomeStringPropertiesClass
{
public int IntProperty { get; set; }
public string StringProperty1 { get; set; }
public string StringProperty2 { get; set; }
public string StringProperty3 { get; set; }
public List<SomeStringPropertiesClass> Children { get; set; } = new();
}
[TestMethod]
public void SomeStringProperties_DoesTrimStrings()
{
// Arrange
var test = new SomeStringPropertiesClass()
{
IntProperty = 42,
StringProperty1 = "Already trimmed string",
StringProperty2 = " Needs trimming ",
StringProperty3 = "",
Children = new()
{
new SomeStringPropertiesClass()
{
StringProperty1 = " Child that needs trimming ",
StringProperty2 = null,
StringProperty3 = " Child that needs trimming . ",
Children = new()
{
null,
new SomeStringPropertiesClass()
{
StringProperty2 = " Grandchild that needs trimming ",
},
null
}
}
}
};
// Act
test.TrimAllStrings();
// Assert
test.IntProperty.Should().Be(42);
test.StringProperty1.Should().Be("Already trimmed string");
test.StringProperty2.Should().Be("Needs trimming");
test.StringProperty3.Should().BeEmpty();
test.Children[0].StringProperty1.Should().Be("Child that needs trimming");
test.Children[0].StringProperty2.Should().BeNull();
test.Children[0].StringProperty3.Should().Be("Child that needs trimming .");
test.Children[0].Children[1].StringProperty1.Should().BeNull();
test.Children[0].Children[1].StringProperty2.Should().Be("Grandchild that needs trimming");
test.Children[0].Children[1].StringProperty3.Should().BeNull();
}
}
}
Thanks #Teter28 for the idea with code generation. It's much more effective than solutions with reflection.
The provided code example does not work. Here's ready for use example.
public static class Trimmer<T>
{
private static readonly Action<T> TrimAllStringFieldsAction = CreateTrimAllStringPropertiesMethod();
public static void TrimAllStringProperties(T parameter)
{
TrimAllStringFieldsAction(parameter);
}
private static Action<T> CreateTrimAllStringPropertiesMethod()
{
var parameter = Expression.Parameter(typeof(T));
var trimMethod = typeof(string).GetMethod(nameof(string.Trim), Type.EmptyTypes);
return Expression.Lambda<Action<T>>(
Expression.Block(
parameter.Type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(propertyInfo => propertyInfo.PropertyType == typeof(string))
.Select(propertyInfo => Expression.Assign(
Expression.Property(parameter, propertyInfo),
Expression.Call(Expression.Property(parameter, propertyInfo), trimMethod!)))),
parameter)
.Compile();
}
}
I examine the properties of an object via reflection and continue processing the data type of each property. Here is my (reduced) source:
private void ExamineObject(object o)
{
Type type = default(Type);
Type propertyType = default(Type);
PropertyInfo[] propertyInfo = null;
type = o.GetType();
propertyInfo = type.GetProperties(BindingFlags.GetProperty |
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance);
// Loop over all properties
for (int propertyInfoIndex = 0; propertyInfoIndex <= propertyInfo.Length - 1; propertyInfoIndex++)
{
propertyType = propertyInfo[propertyInfoIndex].PropertyType;
}
}
My problem is, that I newly need to handle nullable properties, but I have no clue how to get the type of a nullable property.
possible solution:
propertyType = propertyInfo[propertyInfoIndex].PropertyType;
if (propertyType.IsGenericType &&
propertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
propertyType = propertyType.GetGenericArguments()[0];
}
Nullable.GetUnderlyingType(fi.FieldType) will do the work for you check below code for do the thing you want
System.Reflection.FieldInfo[] fieldsInfos = typeof(NullWeAre).GetFields();
foreach (System.Reflection.FieldInfo fi in fieldsInfos)
{
if (fi.FieldType.IsGenericType
&& fi.FieldType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
{
// We are dealing with a generic type that is nullable
Console.WriteLine("Name: {0}, Type: {1}", fi.Name, Nullable.GetUnderlyingType(fi.FieldType));
}
}
foreach (var info in typeof(T).GetProperties())
{
var type = info.PropertyType;
var underlyingType = Nullable.GetUnderlyingType(type);
var returnType = underlyingType ?? type;
}
As pointed out by Yves M. it is as simple as below.
var properties = typeof(T).GetProperties();
foreach (var prop in properties)
{
var propType = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType;
var dataType = propType.Name;
}
I am using a loop to go through all class properties to get the property type. I use the following code:
public Dictionary<string, string> GetClassFields(TEntity obj)
{
Dictionary<string, string> dctClassFields = new Dictionary<string, string>();
foreach (PropertyInfo property in obj.GetType().GetProperties())
{
if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>) && property.PropertyType.GetGenericArguments().Length > 0)
dctClassFields.Add(property.Name, property.PropertyType.GetGenericArguments()[0].FullName);
else
dctClassFields.Add(property.Name, property.PropertyType.FullName);
}
return dctClassFields;
}
This method is easy, fast and safe
public static class PropertyInfoExtension {
public static bool IsNullableProperty(this PropertyInfo propertyInfo)
=> propertyInfo.PropertyType.Name.IndexOf("Nullable`", StringComparison.Ordinal) > -1;
}