I've got a deeply nested private fields chain which I'd like to iterate recursively to get the value of some target field.
How can this be done?
For example:
public class A
{
private B b;
public A(B b) { this.b = b; }
}
public class B
{
private C[] cItems;
public B(C[] cItems) { this.cItems = cItems; }
}
public class C
{
private string target; // <-- get this value
public C(int target) { this.target = val; }
}
public static void GetFieldValueByPath(object targetObj, string targetFieldPath)
{
// how to do it? I self-answer below
}
Usage will be:
public void DoSomething(A a)
{
var val = GetFieldValueByPath(a, "b.cItems[2].target");
}
Notes:
There is a related question about recursively getting properties, but not fields. But even then, it doesn't support array fields.
Related questions such as this one for getting fields are not recursive.
The code works for your example, but you may need to change it in case of having Dictionaries
public static object GetFieldValueByPath(object targetObj, string targetFieldPath)
{
var fieldNames = targetFieldPath.Split('.');
var type = targetObj.GetType();
foreach (var fieldName in fieldNames)
{
string name = fieldName;
int? objectIndex = default;
if (name.Contains('['))//getting fieldName without indexer
{
int indexerStart = name.IndexOf('[');
int indexerEnd = name.IndexOf(']');
objectIndex = int.Parse(name.Substring(indexerStart + 1, indexerEnd-indexerStart - 1));
name = name.Substring(0, indexerStart);
}
var field = type.GetField(name, BindingFlags.NonPublic | BindingFlags.Instance);
if (objectIndex.HasValue)//here we know that field is collection
{
targetObj=((IList<object>)field.GetValue(targetObj))[0];//getting item by index
type = targetObj.GetType();
}
else
{
targetObj = field.GetValue(targetObj);
type = field.FieldType;
}
}
return targetObj;
}
OfirD's answer is on the right track, but it won't work. Not only does it not compile, but C[] does not implement IList<object>.
It also has quite a few scenarios that it does not account for. (I have not updated his code to account for these scenarios)
What if the array does not get indexed by an integer?
What if it is a jagged array?
What if the path points to properties instead of fields?
I've updated his code to work:
public static object GetFieldValueByPath(object obj, string fieldPath)
{
var flags =
BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
var splitted = fieldPath.Split('.');
var current = splitted[0];
int? index = null;
// Support getting a certain index in an array field
var match = Regex.Match(current, #"\[([0-9]+)\]");
if (match.Groups.Count > 1)
{
current = fieldPath.Substring(0, match.Groups[0].Index);
index = int.Parse(match.Groups[1].Value);
}
var value = obj.GetType().GetField(current, flags).GetValue(obj);
if (value == null)
{
return null;
}
if (splitted.Length == 1)
{
return value;
}
if (index != null)
{
value = Index(value, index.Value);
}
return GetFieldValueByPath(value, string.Join(".", splitted.Skip(1)));
}
static object Index(object obj, int index)
{
var type = obj.GetType();
foreach (var property in obj.GetType().GetProperties())
{
var indexParams = property.GetIndexParameters();
if (indexParams.Length != 1) continue;
return property.GetValue(obj, new object[] { index });
}
throw new Exception($"{type} has no getter of the format {type}[int]");
}
Here's the way to do it (note the improvement over other answers, achieved using regex to prepare the path-parts ahead of time):
public static object GetFieldValueByPath(object obj, string fieldPath)
{
var flags = BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
var parts = Regex.Matches(fieldPath, #"([^.\[]+)(?:\[(.*?)\])?").Cast<Match>().Select(match => match.Groups).ToList();
return GetFieldValueByPathParts(obj, parts, flags);
}
private static object GetFieldValueByPathParts(object obj, List<GroupCollection> parts, BindingFlags flags)
{
if (obj == null || parts.Count == 0) return obj;
var field = new Field(name: parts[0][1].Value, value: (object)null, index: parts[0][2].Value);
try
{
field.Value = obj.GetType().GetField(field.Name, flags).GetValue(obj);
}
catch (NullReferenceException ex)
{
throw new Exception($"Wrong path provided: field '{field.Name}' does not exist on '{obj}'");
}
field = TrySetEnumerableValue(field);
return GetFieldValueByPathParts(field.Value, parts.Skip(1).ToList(), flags);
}
private static Field TrySetEnumerableValue(Field field)
{
if (field.Value != null && field.Index != null)
{
var enumerable = ((IEnumerable)field.Value).Cast<object>();
field.Value = field.Index <= enumerable.Count() ? enumerable.ElementAt(field.Index.Value) : null;
}
return field;
}
Here's the definition of the Field helper class:
public class Field
{
public string Name { get; set; }
public object Value { get; set; }
public int? Index { get; set; }
public Field(string name, object value, string index)
{
Name = name;
Value = value;
Index = int.TryParse(index, out int parsed) ? parsed : (int?)null;
}
}
Usage (live demo):
public static void Main(string[] s)
{
var a1 = new A(new B(new C[] { new C(1), new C(2), new C(3) } ) );
Console.WriteLine(GetFieldValueByPath(a1, "b.cItems[2].target"));
var a2 = new A(new B(new C[] { } ) );
Console.WriteLine(GetFieldValueByPath(a2, "b.cItems[2].target"));
var a3 = new A(new B(null) );
Console.WriteLine(GetFieldValueByPath(a3, "b.cItems[2].target"));
}
Related
I have three C# (using .NET 3.5 for a legacy sw) objects of the same class (A, B, C) that has all public properties (string, int, short, byte, datetime, double)
I need to create a fourth (D) by merging the "three" objects.
If A has a property set (not null or empty) I have to set it in D, otherwise I have to check B and then as last C.
What's the most effective and elegant way to do it?
I've read about Reflection is that the right way?
Reflection is a way to do it. Below is an example; probably not the most elegant, but it can be built upon:
using System
using System.Reflection;
namespace Test
{
class Program
{
static void Main(string[] args)
{
Car A = new Car
{
Make = "Volvo"
};
Car B = new Car
{
Year = 2001,
CreateDate = DateTime.Now
};
Car C = new Car
{
ShortValue = 1,
MSRP = 20000,
ByteValue = 10
};
Car D = new Car();
Mapper mapobj = new Mapper();
D = mapobj.Map<Car>(A);
D = mapobj.Compare<Car>(B, D);
D = mapobj.Compare<Car>(C, D);
// Car D now has all the initialized properties of A,B,C
}
public class Mapper
{
public T Map<T>(T data)
{
var _result = (T)Activator.CreateInstance(typeof(T), new object[] { });
foreach (PropertyInfo propertyInfo in typeof(T).GetProperties())
{
if (typeof(T).GetProperty(propertyInfo.Name) != null)
typeof(T)
.GetProperty(propertyInfo.Name,
BindingFlags.IgnoreCase |
BindingFlags.Instance |
BindingFlags.Public)
.SetValue(_result,
propertyInfo.GetValue(data));
}
return _result;
}
public T Compare<T>(T data, T data2)
{
var _result = (T)Activator.CreateInstance(typeof(T), new object[] { });
foreach (PropertyInfo propertyInfo in typeof(T).GetProperties())
{
if (typeof(T).GetProperty(propertyInfo.Name) != null)
{
bool isnullvalue = false;
DateTime zerodate = new DateTime();
switch (propertyInfo.PropertyType.Name)
{
case "String":
if ((string)propertyInfo.GetValue(data) != null && (string)propertyInfo.GetValue(data2) == null)
isnullvalue = true;
break;
case "Int32":
if ((Int32)propertyInfo.GetValue(data) != 0 && (Int32)propertyInfo.GetValue(data2) == 0)
isnullvalue = true;
break;
case "Int16":
if ((Int16)propertyInfo.GetValue(data) != 0 && (Int16)propertyInfo.GetValue(data2) == 0)
isnullvalue = true;
break;
case "Byte":
if ((Byte)propertyInfo.GetValue(data) != 0 && (Byte)propertyInfo.GetValue(data2) == 0)
isnullvalue = true;
break;
case "Double":
if ((Double)propertyInfo.GetValue(data) != 0 && (Double)propertyInfo.GetValue(data2) == 0)
isnullvalue = true;
break;
case "DateTime": // DateTime.Compare(date1, date2)
DateTime time1 = (DateTime)propertyInfo.GetValue(data);
DateTime time2 = (DateTime)propertyInfo.GetValue(data2);
if (DateTime.Compare(time1, zerodate) != 0 && DateTime.Compare(time2, zerodate) == 0)
isnullvalue = true;
break;
default:
Console.WriteLine("No handler for type {} found");
Console.ReadLine();
Environment.Exit(-1);
break;
}
if (isnullvalue)
{
typeof(T).GetProperty(propertyInfo.Name,
BindingFlags.IgnoreCase |
BindingFlags.Instance |
BindingFlags.Public)
.SetValue(_result,
propertyInfo.GetValue(data));
}
else
{
typeof(T).GetProperty(propertyInfo.Name,
BindingFlags.IgnoreCase |
BindingFlags.Instance |
BindingFlags.Public)
.SetValue(_result,
propertyInfo.GetValue(data2));
}
}
}
return _result;
}
}
public class Car
{
public string Make { get; set; }
public int Year { get; set; }
public short ShortValue { get; set; }
public byte ByteValue { get; set; }
public DateTime CreateDate { get; set; }
public double MSRP { get; set; }
}
}
}
You could undoubtedly make a reflection-based solution work here, but you might not need it. If you know the type being merged in advanced, you can write a very simple mapping function to handle this.
For example, given a simple class...
class MyClassToMap
{
public string MyString { get; set; }
public int MyInt { get; set; }
}
you could write a simple method...
MyClassToMap Map(params MyClassToMap[] toMap)
{
var mapped = new MyClassToMap();
foreach (var m in toMap)
{
// 'default' is shorthand for a type's uninitalized value. In the case of
// string, it resolves to "null", and in the case of int, it resolves to 0.
// You could also use the literal values here, if you prefer.
// Note that for C# versions < 7.1, you must specify the type--eg "default(string)".
// See: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/default
if (m.MyString != default) mapped.MyString = m.MyString;
if (m.MyInt != default) mapped.MyInt = m.MyInt;
}
return mapped;
}
and call it like so...
var a = new MyClassToMap { MyString = "foo", MyInt = 0 };
var b = new MyClassToMap { MyString = "bar", MyInt = 100 };
var c = new MyClassToMap { MyString = null, MyInt = 0 };
var mapped = Map(a, b, c);
Console.WriteLine($"MyString = {mapped.MyString}, MyInt = {mapped.MyInt}");
// prints: { MyString = "bar", MyInt = 100 };
You could use reflection for the purpose. For example, Please check following code (Inline comments added to method explaining the approach)
public T AssignProperties<T>(params T[] sources)
{
// Types of properties that has to be copied
var filteredPropertyTypes = new []{typeof(int),typeof(string),typeof(short),typeof(byte),typeof(DateTime),typeof(double)};
// Create Default Instance of T
var newInstance = (T)Activator.CreateInstance(typeof(T));
// Iterate through each property
foreach(var property in typeof(T).GetProperties().Where(x=>filteredPropertyTypes.Contains(x.PropertyType)))
{
// Get the default Value of the Type and get the first instance in sources, which has a non-default value for the property
var defaultValue = property.PropertyType.IsValueType ? Convert.ChangeType(Activator.CreateInstance(property.PropertyType),property.PropertyType) : null;
if(sources.Any(x=>!Convert.ChangeType(property.GetValue(x),property.PropertyType).Equals(defaultValue)))
{
var newValue = property.GetValue(sources.First(x=>!Convert.ChangeType(property.GetValue(x),property.PropertyType).Equals(defaultValue)));
property.SetValue(newInstance,newValue);
}
}
return newInstance;
}
You could now use the method for N number of instances(A,B,C...), with instances passed in order in which they need to be processed.
var newInstance = AssignProperties(instanceA,instanceB,instanceC);
Demo Code
I want to consume an ASP.NET Core Web API method that includes [FromQuery] parameters.
Since the format is somewhat unusual, I figured there would exist a library function that would take a complex type and generate the query string formatted text - however, I can't find one.
IOW, given a controller method defined like this:
[HttpGet("testing")]
public bool Testing([FromQuery]X x)
{
return (x?.Ys[1]?.Zs[1]?.Bs[3] == 3 && x?.Ys[1]?.Zs[0]?.A == 4);
}
And an X defined like this:
public class X
{
public Y[] Ys { get; set; }
}
public class Y
{
public Z[] Zs { get; set; }
}
public class Z
{
public int A { get; set; }
public int[] Bs { get; set; }
}
First of all, what's an example of what ASP.NET [FromQuery] is expecting to encounter in the query string in order to return true?
Secondly, is there a function somewhere that can serialize an object appropriately into whatever ASP.NET is expecting, or do I need to write one?
You can use the following "serializer"
public class QueryStringSerializer
{
private static bool IsPrimitive(object obj)
{
return obj.GetType().IsPrimitive || obj is string || obj is Guid;
}
private static bool IsEnumerable(object obj)
{
return obj is IEnumerable && !IsPrimitive(obj);
}
private static bool IsComplex(object obj)
{
return !(obj is IEnumerable) && !IsPrimitive(obj);
}
private static StringBuilder ToQueryStringInternal(object obj, string prop = null)
{
StringBuilder queryString = new StringBuilder();
//skip null values
if (obj == null)
return queryString;
Type type = obj.GetType();
PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
if (IsEnumerable(obj))
{
int i = 0;
foreach (object item in obj as IEnumerable)
{
string query = ToQueryStringInternal(item, $"{prop}[{i}]").ToString();
queryString.Append(query);
i++;
}
}
else if (IsComplex(obj))
{
foreach (PropertyInfo property in properties)
{
string propName = property.Name;
object value = property.GetValue(obj);
string name = prop == null ? propName : $"{prop}.{propName}";
string query = ToQueryStringInternal(value, name).ToString();
queryString.Append(query);
}
}
else
{
string encoded = HttpUtility.UrlEncode(Convert.ToString(obj));
queryString.Append($"{prop}={encoded}&");
}
return queryString;
}
public static string ToQueryString(object obj, string propertyName = null)
{
StringBuilder queryString = ToQueryStringInternal(obj, propertyName);
queryString.Length--;
return queryString.ToString();
}
}
Usage
var x = new X
{
Ys = new Y[] {
new Y {
Zs = new Z[] { new Z { } }
},
new Y {
Zs = new Z[] {
new Z { },
new Z {
A = 1,
Bs = new int[] { 0, 1, 2, 3 }
}
}
}
}
};
string query = QueryStringSerializer.ToQueryString(x);
Result
Ys[0].Zs[0].A=0&Ys[1].Zs[0].A=0&Ys[1].Zs[1].A=1&Ys[1].Zs[1].Bs[0]=0&Ys[1].Zs[1].Bs[1]=1&Ys[1].Zs[1].Bs[2]=2&Ys[1].Zs[1].Bs[3]=3
Caution
The serializer may still contain various bags. Also, do not create array with "gaps" such as
var q = new X[] {
new X { },
null, //a gap
new X { }
};
The result will be technically correct but ASP.NET model binder will bind only the first element properly.
I'm trying fill a list of object from data table using the following method
public static List<T> toList<T>(this DataTable table) where T : new()
{
try
{
List<T> list = new List<T>();
foreach (var row in table.AsEnumerable())
{
var obj = new T();
foreach (var prop in typeof(T).GetProperties())
{
PropertyInfo propertyInfo = obj.GetType().GetProperty(prop.Name);
Type targetType = propertyInfo.PropertyType;
if (table.Columns.Contains(prop.Name))
{
try
{
object value = row[prop.Name];
if (value != null)
{
if (value.GetType() == typeof(string))
{
if (string.IsNullOrWhiteSpace(value.ToString()))
{
value = null;
}
}
if (targetType.IsGenericType && targetType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
{
targetType = Nullable.GetUnderlyingType(targetType);
}
value = Convert.ChangeType(value, targetType);
propertyInfo.SetValue(obj, value);
}
}
catch
{
continue;
}
}
}
list.Add(obj);
}
return list;
}
catch (Exception ex)
{
return null;
}
}
I've the following models
public class A
{
public string str1 {get;set;}
public int int1 {get;set;}
public DateTime dateTime1 {get;set;}
}
public class B
{
public string str2 {get;set;}
public int int2 {get;set;}
public DateTime dateTime2 {get;set;}
public A vara {get;set;}
}
My Data table looks like this
+-----------+-----------+-----------+-----------+---------------+---------------+
| str1 | str2 | int1 | int2 | dateTime1 | dateTime2 |
+-----------+-----------+-----------+-----------+---------------+---------------+
| "abc" | "def" | 1 | 2 | NULL | NULL |
+-----------+-----------+-----------+-----------+---------------+---------------+
All this works fine If I use
List<B> list = dataTable.toList<B>();
But I also want to set the value of vara in each element of list.
How can I check that if a Type is custom defined type?
I can not use Type.IsClass because it is true for string too.
If I can detect that a property is of Custom Class Type then I can fill that's value using the same method.
I hope that I've explained it well.
I was able create a following generic solution
public static List<T> toList<T>(this DataTable table) where T : new()
{
try
{
var list = table.toList(typeof(T));
var newLIst = list.Cast<T>().ToList();
return newLIst;
}
catch
{
return null;
}
}
public static List<object> toList(this DataTable table, Type type)
{
try
{
List<object> list = new List<object>();
foreach (var row in table.AsEnumerable())
{
var obj = row.toObject(type);
list.Add(obj);
}
return list;
}
catch
{
return null;
}
}
public static object toObject(this DataRow row, Type type, string sourcePropName = "")
{
try
{
var obj = Activator.CreateInstance(type);
var props = type.GetProperties();
foreach (var prop in props)
{
PropertyInfo propertyInfo = type.GetProperty(prop.Name);
Type targetType = propertyInfo.PropertyType;
string propName = prop.Name;
if (!string.IsNullOrWhiteSpace(sourcePropName))
{
propName = sourcePropName + "__" + propName;
if (!row.Table.Columns.Contains(propName))
{
propName = prop.Name;
}
}
if (row.Table.Columns.Contains(propName))
{
try
{
object value = row[propName];
if (value != null)
{
if (value.GetType() == typeof(string))
{
if (string.IsNullOrWhiteSpace(value.ToString()))
{
value = null;
}
}
targetType = targetType.handleNullableType();
value = Convert.ChangeType(value, targetType);
propertyInfo.SetValue(obj, value);
}
}
catch
{
continue;
}
}
else
if (targetType.IsClass && targetType != typeof(string))
{
if (targetType.IsGenericList())
{
Type ltype = targetType.GetProperty("Item").PropertyType;
object value = row.toObject(ltype, propName);
if (value == null)
{
continue;
}
var valList = new List<object> { value }.ConvertList(targetType);
try
{
propertyInfo.SetValue(obj, valList);
}
catch (Exception ex)
{
log.Error(ex);
}
}
else
{
object value = row.toObject(targetType, propName);
propertyInfo.SetValue(obj, value);
}
}
}
return obj;
}
catch
{
return null;
}
}
public static object ConvertList(this List<object> value, Type type)
{
IList list = (IList)Activator.CreateInstance(type);
foreach (var item in value)
{
list.Add(item);
}
return list;
}
In order to fill all properties of class B including vara property, I must prefix the names of columns that belong to vara properties and added splitter ___
Therefore, the about table should be as follows
+-----------------+-----------+-----------------+-----------+---------------------+---------------+
| vara__str1 | str2 | vara__int1 | int2 | vara__dateTime1 | dateTime2 |
+-----------------+-----------+-----------------+-----------+---------------------+---------------+
| "abc" | "def" | 1 | 2 | NULL | NULL |
+-----------------+-----------+-----------------+-----------+---------------------+---------------+
I am trying update a number of properties of one object from another and I wind up repeating this same code over and over again (i am showing an example with Name and LastName but i have 15 other properties with similar code).
But its important to Note that its NOT all properties so i can't blindly just copy everything.
public class Person
{
public bool UpdateFrom(Person otherPerson)
{
if (!String.IsNullOrEmpty(otherPerson.Name))
{
if (Name!= otherPerson.Name)
{
change = true;
Name = otherPerson.Name;
}
}
if (!String.IsNullOrEmpty(otherPerson.LastName))
{
if (LastName!= otherPerson.LastName)
{
change = true;
LastName = otherPerson.LastName;
}
}
return change;
}
}
is there a more elegant way to writing this code?
You could use an Expression to define which field you want to access, the code to handle the updates would look like this:-
Person one = new Person {FirstName = "First", LastName = ""};
Person two = new Person {FirstName = "", LastName = "Last"};
Person three = new Person ();
bool changed = false;
changed = SetIfNotNull(three, one, p => p.FirstName) || changed;
changed = SetIfNotNull(three, one, p => p.LastName) || changed;
changed = SetIfNotNull(three, two, p => p.FirstName) || changed;
changed = SetIfNotNull(three, two, p => p.LastName) || changed;
Note that the order in the || expression matters since .NET will short-circuit the evaluation if it can. Or as Ben suggests in the comments below, use changed |= ... as a simpler alternative.
The SetIfNotNull method relies on this other method that does a bit of Expression magic to convert a getter ino a setter.
/// <summary>
/// Convert a lambda expression for a getter into a setter
/// </summary>
public static Action<T, U> GetSetter<T, U>(Expression<Func<T, U>> expression)
{
var memberExpression = (MemberExpression)expression.Body;
var property = (PropertyInfo)memberExpression.Member;
var setMethod = property.GetSetMethod();
var parameterT = Expression.Parameter(typeof(T), "x");
var parameterU = Expression.Parameter(typeof(U), "y");
var newExpression =
Expression.Lambda<Action<T, U>>(
Expression.Call(parameterT, setMethod, parameterU),
parameterT,
parameterU
);
return newExpression.Compile();
}
public static bool SetIfNotNull<T> (T destination, T source,
Expression<Func<T, string>> getter)
{
string value = getter.Compile()(source);
if (!string.IsNullOrEmpty(value))
{
GetSetter(getter)(destination, value);
return true;
}
else
{
return false;
}
}
Using Func and Action delegates you can do it like this:
public class Person
{
public string Name { get; set; }
public string LastName { get; set; }
public bool UpdateFrom(Person otherPerson)
{
bool change = false;
change = Check(otherPerson.Name, p => p.Name, (p, val) => p.Name = val);
change = change ||
Check(otherPerson.LastName, p => p.LastName, (p, val) => p.LastName = val);
return change;
}
public bool Check(string value, Func<Person, string> getMember, Action<Person, string> action)
{
bool result = false;
if (!string.IsNullOrEmpty(value))
{
if (getMember(this) != value)
{
result = true;
action(this, value);
}
}
return result;
}
}
You can use reflecton to do it.. here's an example implementation (need to add extra code to handle arrays etc.)
public class Person
{
public bool UpdateFromOther(Person otherPerson)
{
var properties =
this.GetType()
.GetProperties(
BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty
| BindingFlags.GetProperty);
var changed = properties.Any(prop =>
{
var my = prop.GetValue(this);
var theirs = prop.GetValue(otherPerson);
return my != null ? !my.Equals(theirs) : theirs != null;
});
foreach (var propertyInfo in properties)
{
propertyInfo.SetValue(this, propertyInfo.GetValue(otherPerson));
}
return changed;
}
public string Name { get; set; }
}
[Test]
public void Test()
{
var instance1 = new Person() { Name = "Monkey" };
var instance2 = new Person() { Name = "Magic" };
var instance3 = new Person() { Name = null};
Assert.IsFalse(instance1.UpdateFromOther(instance1), "No changes should be detected");
Assert.IsTrue(instance2.UpdateFromOther(instance1), "Change is detected");
Assert.AreEqual("Monkey",instance2.Name, "Property updated");
Assert.IsTrue(instance3.UpdateFromOther(instance1), "Change is detected");
Assert.AreEqual("Monkey", instance3.Name, "Property updated");
}
This is just my comment typed out, you can refer to the comments to your question about further details about this technique.
Define this class:
[AttributeUsage(AttributeTargets.Property)]
public sealed class CloningAttribute : Attribute
{
}
In your Person class:
[Cloning] // <-- applying the attribute only to desired properties
public int Test { get; set; }
public bool Clone(Person other)
{
bool changed = false;
var properties = typeof(Person).GetProperties();
foreach (var prop in properties.Where(x => x.GetCustomAttributes(typeof(CloningAttribute), true).Length != 0))
{
// get current values
var myValue = prop.GetValue(this, null);
var otherValue = prop.GetValue(other, null);
if (prop.PropertyType == typeof(string))
{
// special treatment for string:
// ignore if null !!or empty!!
if (String.IsNullOrEmpty((string)otherValue))
{
continue;
}
}
else
{
// do you want to copy if the other value is null?
if (otherValue == null)
{
continue;
}
}
// compare and only check 'changed' if they are different
if (!myValue.Equals(otherValue))
{
changed = true;
prop.SetValue(this, otherValue, null);
}
}
return changed;
}
You can create generic rewriting tool with will look on properties with particular attribute:
public class Updater
{
public static bool Update(object thisObj, object otherObj)
{
IEnumerable<PropertyInfo> props = thisObj.GetType().GetProperties().Where(
prop => Attribute.IsDefined(prop, typeof(UpdateElementAttribute)));
bool change = false;
foreach (var prop in props)
{
object value = prop.GetValue(otherObj);
if (value != null && (value is string || string.IsNullOrWhiteSpace((string)value)))
{
if (!prop.GetValue(thisObj).Equals(value))
{
change = true;
prop.SetValue(thisObj, value);
}
}
}
return change;
}
}
And then just use it:
public class Person
{
public bool UpdateFrom(Person otherPerson)
{
return Updater.Update(this, otherPerson);
}
[UpdateElement]
public string Name { get; set; }
[UpdateElement]
public string LastName { get; set; }
}
I have the following two classes:
public class Address
{
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
}
public class Employee
{
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public Address EmployeeAddress { get; set; }
}
I have an instance of the employee class as follows:
var emp1Address = new Address();
emp1Address.AddressLine1 = "Microsoft Corporation";
emp1Address.AddressLine2 = "One Microsoft Way";
emp1Address.City = "Redmond";
emp1Address.State = "WA";
emp1Address.Zip = "98052-6399";
var emp1 = new Employee();
emp1.FirstName = "Bill";
emp1.LastName = "Gates";
emp1.EmployeeAddress = emp1Address;
I have a method which gets the property value based on the property name as follows:
public object GetPropertyValue(object obj ,string propertyName)
{
var objType = obj.GetType();
var prop = objType.GetProperty(propertyName);
return prop.GetValue(obj, null);
}
The above method works fine for calls like GetPropertyValue(emp1, "FirstName") but if I try GetPropertyValue(emp1, "Address.AddressLine1") it throws an exception because objType.GetProperty(propertyName); is not able to locate the nested object property value. Is there a way to fix this?
public object GetPropertyValue(object obj, string propertyName)
{
foreach (var prop in propertyName.Split('.').Select(s => obj.GetType().GetProperty(s)))
obj = prop.GetValue(obj, null);
return obj;
}
Thanks, I came here looking for an answer to the same problem. I ended up modifying your original method to support nested properties. This should be more robust than having to do nested method calls which could end up being cumbersome for more than 2 nested levels.
This will work for unlimited number of nested property.
public object GetPropertyValue(object obj, string propertyName)
{
var _propertyNames = propertyName.Split('.');
for (var i = 0; i < _propertyNames.Length; i++)
{
if (obj != null)
{
var _propertyInfo = obj.GetType().GetProperty(_propertyNames[i]);
if (_propertyInfo != null)
obj = _propertyInfo.GetValue(obj);
else
obj = null;
}
}
return obj;
}
Usage:
GetPropertyValue(_employee, "Firstname");
GetPropertyValue(_employee, "Address.State");
GetPropertyValue(_employee, "Address.Country.Name");
var address = GetPropertyValue(GetPropertyValue(emp1, "Address"), "AddressLine1");
Object Employee doesn't have a single property named "Address.AddressLine1", it has a property named "Address", which itself has a property named "AddressLine1".
I use this method to get the values from properties (unlimited number of nested property) as below:
"Property"
"Address.Street"
"Address.Country.Name"
public static object GetPropertyValue(object src, string propName)
{
if (src == null) throw new ArgumentException("Value cannot be null.", "src");
if (propName == null) throw new ArgumentException("Value cannot be null.", "propName");
if(propName.Contains("."))//complex type nested
{
var temp = propName.Split(new char[] { '.' }, 2);
return GetPropertyValue(GetPropertyValue(src, temp[0]), temp[1]);
}
else
{
var prop = src.GetType().GetProperty(propName);
return prop != null ? prop.GetValue(src, null) : null;
}
}
Here the Fiddle: https://dotnetfiddle.net/PvKRH0
Yet another variation to throw out there. Short & sweet, supports arbitrarily deep properties, handles null values and invalid properties:
public static object GetPropertyVal(this object obj, string name) {
if (obj == null)
return null;
var parts = name.Split(new[] { '.' }, 2);
var prop = obj.GetType().GetProperty(parts[0]);
if (prop == null)
throw new ArgumentException($"{parts[0]} is not a property of {obj.GetType().FullName}.");
var val = prop.GetValue(obj);
return (parts.Length == 1) ? val : val.GetPropertyVal(parts[1]);
}
Get Nest properties e.g., Developer.Project.Name
private static System.Reflection.PropertyInfo GetProperty(object t, string PropertName)
{
if (t.GetType().GetProperties().Count(p => p.Name == PropertName.Split('.')[0]) == 0)
throw new ArgumentNullException(string.Format("Property {0}, is not exists in object {1}", PropertName, t.ToString()));
if (PropertName.Split('.').Length == 1)
return t.GetType().GetProperty(PropertName);
else
return GetProperty(t.GetType().GetProperty(PropertName.Split('.')[0]).GetValue(t, null), PropertName.Split('.')[1]);
}
I made an extension method on type for this propose:
public static class TypeExtensions
{
public static PropertyInfo GetSubProperty(this Type type, string treeProperty, object givenValue)
{
var properties = treeProperty.Split('.');
var value = givenValue;
foreach (var property in properties.Take(properties.Length - 1))
{
value = value.GetType().GetProperty(property).GetValue(value);
if (value == null)
{
return null;
}
}
return value.GetType().GetProperty(properties[properties.Length - 1]);
}
public static object GetSubPropertyValue(this Type type, string treeProperty, object givenValue)
{
var properties = treeProperty.Split('.');
return properties.Aggregate(givenValue, (current, property) => current.GetType().GetProperty(property).GetValue(current));
}
}
A Modified version of above to get the multilevel nested properties
private static System.Reflection.PropertyInfo GetProperty(object t, string PropertName, out object Value)
{
Value = "";
var v = t.GetType().GetProperties();
if (t.GetType().GetProperties().Count(p => p.Name == PropertName.Split('.')[0]) == 0)
//throw new ArgumentNullException(string.Format("Property {0}, is not exists in object {1}", PropertName, t.ToString()));
return null;
if (PropertName.Split('.').Length == 1)
{
var Value1 = t.GetType().GetProperty(PropertName).GetValue(t, null);
Value = Value1;//.ToString();
return t.GetType().GetProperty(PropertName);
}
else
{
//return GetProperty(t.GetType().GetProperty(PropertName.Split('.')[0]).GetValue(t, null), PropertName.Split('.')[1], out Value);
return GetProperty(t.GetType().GetProperty(PropertName.Split('.')[0]).GetValue(t, null), PropertName.Substring(PropertName.IndexOf('.') + 1, PropertName.Length - PropertName.IndexOf('.') - 1), out Value);
}
}
This will work for level 1 and level 2 object properties e.g. Firstname and Address.AddressLine1
public object GetPropertyValue(object obj, string propertyName)
{
object targetObject = obj;
string targetPropertyName = propertyName;
if (propertyName.Contains('.'))
{
string[] split = propertyName.Split('.');
targetObject = obj.GetType().GetProperty(split[0]).GetValue(obj, null);
targetPropertyName = split[1];
}
return targetObject.GetType().GetProperty(targetPropertyName).GetValue(targetObject, null);
}
I have a problem with struct type in static class, So I must use this method GetNestedType, this is example code if you know property name, If you want to getAll you can use GetNestedTypes
ExpandoObject in this example just use for dynamic add property and value
private void ExtractValuesFromAppconstants(string keyName)
{
Type type = typeof(YourClass);
var examination = type.GetNestedType(keyName);
if (examination != null)
{
var innerTypes = examination.GetNestedTypes();
foreach (var innerType in innerTypes)
{
Console.Writeline($"{innerType.Name}")
}
}
}
Recursive method, in one line...
object GetPropertyValue(object obj, string propertyName)
{
return propertyName.Contains(".") ? GetPropertyValue(obj.GetType().GetProperty(propertyName.Split(".").First()).GetValue(obj), string.Join(".", propertyName.Split(".").Skip(1))) : obj != null ? obj.GetType().GetProperty(propertyName).GetValue(obj) : null;
}
I found that the code posted by DevT almost did the trick but failed when there are collections involved e.g. Applicant.Addresses[0].FirstLine, so I added some code to fix this. I am sure others can improve upon in.
public static object GetPropertyValue(object src, string propName)
{
if (src == null) throw new ArgumentException("Value cannot be null.", "src");
if (propName == null) throw new ArgumentException("Value cannot be null.", "propName");
if (propName.Contains("."))//complex type nested
{
var temp = propName.Split(new char[] { '.' }, 2);
return GetPropertyValue(GetPropertyValue(src, temp[0]), temp[1]);
}
else
{
if (propName.Contains("["))
{
int iterator_start = propName.IndexOf('[');
int iterator_end = propName.IndexOf(']');
string iterator_value = propName.Substring(iterator_start + 1, iterator_end - iterator_start - 1);
string string_to_remove = "[" + iterator_value + "]";
int iterator_number = Convert.ToInt32(iterator_value);
propName = propName.Replace(string_to_remove, "");
var prop2 = src.GetType().GetProperty(propName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
Type type = prop2.PropertyType;
if (type.IsGenericType && type.GetGenericTypeDefinition()
== typeof(List<>))
{
System.Collections.IList oTheList = (System.Collections.IList)prop2.GetValue(src, null);
return oTheList[iterator_number];
}
}
var prop = src.GetType().GetProperty(propName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
return prop != null ? prop.GetValue(src, null) : null;
}
}