I'm trying to implement the common/general DebuggerDisplay attribute to avoid formatting it for every single usage which would simply show every property of the class. That's what I've achieved so far, my extension method looks like this
public static class DebuggerExtension
{
public static string ToDebuggerString(this object #this)
{
var builder = new StringBuilder();
foreach (var property in #this.GetType().GetProperties())
{
if (property != null)
{
var value = property.GetValue(#this, null)?.ToString();
builder.Append($"{property.Name}: {value}, ");
}
}
return builder.ToString();
}
}
and I'm calling it like this and it basically works:
[DebuggerDisplay($"{{this.ToDebuggerString()}}")]
My questions are:
Is there any existing extension to generate the DebuggerDisplay string? The built in VS implementation just adds the method which returns ToString() which doesn't really help
Can I call the method on some compiler-checked way, not as a string? "this." is obviously not available in the attribute arguments. Maybe any expression?
Any other possible solutions?
Have you tried using 'ToDebuggerString()' again instead of 'ToString()' in extension method?
It may interest you. Visual Studio's Natvis Debugging Framework Tutorial
The example below may be helpful to you. (It may be missing!)
public static class DebuggerExtension
{
public static string ToDebuggerDisplay(this object o)
{
if (object.ReferenceEquals(o, null))
return "null";
else if (o is string)
return string.Format("\"{0}\"", (string)o);
else if ((o is DateTime) || (o is TimeSpan))
return string.Format("{{{0}}}", o);
else if (o is System.Collections.ICollection)
return string.Format("{{Count={0}}}", ((System.Collections.ICollection)o).Count);
else if (o is System.Collections.IEnumerable)
{
int nCount = 0;
System.Collections.IEnumerator e = ((System.Collections.IEnumerable)o).GetEnumerator();
while (e.MoveNext())
{
nCount++;
}
return string.Format("{{Count={0}}}", nCount);
}
Type objType = o.GetType();
if (objType.IsPrimitive || objType.IsEnum)
return o.ToString();
else if (objType.IsArray)
return string.Format("{{Count={0}}}", ((Array)o).Length);
PropertyInfo[] propertyInfos = objType.GetProperties(BindingFlags.Instance
| BindingFlags.Public
| BindingFlags.GetProperty
| BindingFlags.DeclaredOnly);
string retVal = "{";
int ndx = 0;
foreach (PropertyInfo pi in propertyInfos)
{
if (!pi.CanRead)
continue;
DebuggerBrowsableAttribute attr = pi.GetCustomAttribute<DebuggerBrowsableAttribute>();
if (!object.ReferenceEquals(attr, null) && (attr.State == DebuggerBrowsableState.Never))
continue;
if (ndx++ > 0)
retVal += " ";
retVal += string.Format("{0}={1}", pi.Name, pi.GetValue(o).ToDebuggerDisplay());
if (ndx > 2)
{
retVal += " ...";
break;
}
}
retVal += "}";
return (ndx > 0) ? retVal : o.ToString();
}
}
Uses
[DebuggerDisplay(#"{this.ToDebuggerDisplay(),nq}")]
Related
I am trying to recursively iterate over one root class to grab it's values and build a tree structure for visualization.
For this I have a class that can be initialized with an object
public Item(object genericObject, int depth = 0) {
this.depth = depth;
this.Name = genericObject.GetType().Name;
this.Type = genericObject.GetType().ToString();
this.Value = genericObject;
this.subItems = new List<Item>();
foreach (MemberInfo member in Item.GetType().GetMembers())
{
if (member.MemberType != MemberTypes.Method && member.MemberType != MemberTypes.Constructor)
subItems.Add(new Item(member,genericObject, depth + 1));
}
}
and another constructor for recursion
public Item(MemberInfo member,object parentobj, int depth = 0)
{
if (member.MemberType == MemberTypes.Property)
{
this.FieldName = ((PropertyInfo)member).Name;
this.FieldType = ((PropertyInfo)member).PropertyType.ToString();
this.fieldValue = ((PropertyInfo)member).GetValue(parentobj);
}
else
{
this.FieldName = ((FieldInfo)member).Name;
this.FieldType = ((FieldInfo)member).GetValue(parentobj).GetType().Name;
this.fieldValue = ((FieldInfo)member).GetValue(parentobj);
}
switch (member)
{
case PropertyInfo propertyInfo:
case FieldInfo fieldinfo:
bool found = false;
foreach (string typename in browsethroughtheseTypes)
{
if (this.FieldType.ToLower().Contains(typename)) found = true;
}
if (found)
{
this.subItems = new List<Displayable>();
foreach (MemberInfo mem in member.GetType().GetMembers())
{
if (mem.GetType().IsClass)
this.subItems.Add(new Item(mem, depth + 1));
}
}
break;
default:
break;
}
}
For testing I only added list to browsethroughthesetypes
After recursion takes place in the second constructor all I'm seeing is the internal types.
Is there a proper way to achieve this using reflection?
This question already has answers here:
Check two object, of unknown type, for equality, comparing all their fields
(2 answers)
Closed 3 years ago.
I'm trying to make a generic compare function to see if two instances of a class which may hold member variables that are a List<>.
bool IsEqual<T>(T a, T b)
{
var props = typeof(T).GetProperties();
foreach(var property in props)
{
var aVal = property.GetValue(a);
var bVal = property.GetValue(b);
if (aVal as System.Collections.IEnumerable != null)
{
// Convert into lists, do something with SequenceEqual and possibly recursive call to IsEqual
}
if (!Equals(aVal,bVal)) return false;
}
return true;
}
I'm not sure how to convert a given member variable to a proper list, so I can do a SequenceEqual on it to compare the lists.
The classes that I will use in IsEqual() are not native to my program but comes from a DLL.
Does the following code work for you?
public static bool DeepEquals(object a, object b)
{
if (a == null)
{
if (b == null) return true;
else return false;
}
else if (b == null) return false;
if (object.ReferenceEquals(a, b)) return true;
var comparable = a as IComparable;
if (comparable != null)
{
return comparable.CompareTo(b) == 0;
}
var aType = a.GetType();
var bType = b.GetType();
if (aType != bType) return false;
var aEnumerable = a as IEnumerable;
if (aEnumerable != null)
{
var bEnumerable = (IEnumerable)b;
var aEnumerator = aEnumerable.GetEnumerator();
var bEnumerator = bEnumerable.GetEnumerator();
while (true)
{
var amoved = aEnumerator.MoveNext();
var bmoved = bEnumerator.MoveNext();
if (amoved != bmoved) return false;
if (amoved == false && bmoved == false) return true;
if (DeepEquals(aEnumerator.Current, bEnumerator.Current) == false) return false;
}
}
var props = aType.GetProperties();
foreach (var prop in props)
{
if (DeepEquals(prop.GetValue(a), prop.GetValue(b)) == false) return false;
}
return true;
}
Note that this method is not generic. Implementing a generic method is more difficult because you don't know at runtime the type of properties nor the type of the items of collections.
How do i use this function properly i found it on stack without instructions
i declared it as var db = DeepCopy(absenceEntity);
but how do i access the properties within absenceEntity so I can show the orignal values.
I am trying to implment a copy of values to store before savechanges is called is their an easier way to do this its for audit tracking
public static object DeepCopy(object obj)
{
if (obj == null)
return null;
Type type = obj.GetType();
if (type.IsValueType || type == typeof(string))
{
return obj;
}
else if (type.IsArray)
{
Type elementType = Type.GetType(type.FullName.Replace("[]", string.Empty));
var array = obj as Array;
Array copied = Array.CreateInstance(elementType, array.Length);
for (int i = 0; i < array.Length; i++)
{
copied.SetValue(DeepCopy(array.GetValue(i)), i);
}
return Convert.ChangeType(copied, obj.GetType());
}
else if (type.IsClass)
{
object toret = Activator.CreateInstance(obj.GetType());
FieldInfo[] fields = type.GetFields(BindingFlags.Public |
BindingFlags.NonPublic | BindingFlags.Instance);
foreach (FieldInfo field in fields)
{
object fieldValue = field.GetValue(obj);
if (fieldValue == null)
continue;
field.SetValue(toret, DeepCopy(fieldValue));
}
return toret;
}
else
throw new ArgumentException("Unknown type");
}
Either access the properties on absenceEntity before passing it in or simply
var _absenceEntity = DeepCopy(absenceEntity);
and then
_absenceEntity.(specify propperty here)
I have a list of property paths and values as strings in a text file. Is there a mapping tool or serialize that could take a property path string( "Package.Customer.Name" ) and create the object and set the value?
/// <summary>
/// With a data row, create an object and populate it with data
/// </summary>
private object CreateObject(List<DataCell> pRow, string objectName)
{
Type type = Type.GetType(objectName);
if (type == null)
throw new Exception(String.Format("Could not create type {0}", objectName));
object obj = Activator.CreateInstance(type);
foreach (DataCell cell in pRow)
{
propagateToProperty(obj, *propertypath*, cell.CellValue);
}
return obj;
}
public static void propagateToProperty(object pObject, string pPropertyPath, object pValue)
{
Type t = pObject.GetType();
object currentO = pObject;
string[] path = pPropertyPath.Split('.');
for (byte i = 0; i < path.Length; i++)
{
//go through object hierarchy
PropertyInfo pi = t.GetProperty(path[i]);
if (pi == null)
throw new Exception(String.Format("No property {0} on type {1}", path[i], pObject.GetType()));
//an object in the property path
if (i != path.Length - 1)
{
t = pi.PropertyType;
object childObject = pi.GetValue(currentO, null);
if (childObject == null)
{
childObject = Activator.CreateInstance(t);
pi.SetValue(currentO, childObject, null);
}
currentO = childObject;
//the target property
else
{
if (pi.PropertyType.IsEnum && pValue.GetType() != pi.PropertyType)
pValue = Enum.Parse(pi.PropertyType, pValue.ToString());
if (pi.PropertyType == typeof(Guid) && pValue.GetType() != pi.PropertyType)
pValue = new Guid(pValue.ToString());
if (pi.PropertyType == typeof(char))
if (pValue.ToString().Length == 0)
pValue = ' ';
else
pValue = pValue.ToString()[0];
pi.SetValue(currentO, pValue, null);
}
}
}
Thats what I use. I just had it open in VS, maybe it can help you. The propertyPath is a placeholder for your property path, i cut that out from my code.
If you re looking for a more robust solution, Automapper would probably work nicely, as suggested by someone else already.
I have a framework that handles comunication with the database. It calls my SPs (search, ins, del, upd) filling the parameters automatically by matching an object's property name with the parameter's name. Everything was working great until I got a string value that contains only numbers, and the first number is zero. The code is automatically removing leading zeros, even though both my property and the DB column are string.
Here is a portion of my code:
var property = from p in entityProperties
where p.Name.ToUpper().Equals(dvSchema[index][0].ToString().ToUpper())
select p;
object propertyNewValue;
if (property.First().PropertyType.BaseType != typeof(Enum))
propertyNewValue = dr[index];
...
property.First().SetValue(businessEntity, propertyNewValue, null);
I'm using .NET 3.5 and SQL Server2008
Does anyone know how to avoid this, hopefully other than adding an if to check if the property type is string?
Thanks in advance!
EDIT
Here's my full procedure, I don't explicitly cast the property anywhere:
public virtual void MapEntityFromDataBase(BE businessEntity, IDataReader dr)
{
DataView dvSchema = dr.GetSchemaTable().DefaultView;
Type thisType = businessEntity.GetType();
while (thisType != typeof(Object))
{
PropertyInfo[] entityProperties = thisType.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
for (int index = 0; index < dvSchema.Count; index++)
{
if (dr[index] != DBNull.Value)
{
var property = from p in entityProperties
where p.Name.ToUpper().Equals(dvSchema[index][0].ToString().ToUpper())
select p;
if (property.Count() > 0)
{
object propertyNewValue;
if (property.First().PropertyType.BaseType != typeof(Enum))
{
propertyNewValue = dr[index];
}
else //enums throw a cast exception
{
if (dr[index].GetType() == typeof(string) || dr[index].GetType() == typeof(char))
propertyNewValue = Enum.Parse(property.First().PropertyType, ((int)dr[index].ToString()[0]).ToString());
else
propertyNewValue = Enum.Parse(property.First().PropertyType, dr[index].ToString());
}
if (property.First().CanWrite)
{
if (property.First().PropertyType.IsGenericType && property.First().PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
Type[] typeCol = property.First().PropertyType.GetGenericArguments();
Type nullableType;
if (typeCol.Length > 0)
{
nullableType = typeCol[0];
if (nullableType.BaseType == typeof(Enum))
{
propertyNewValue = Enum.Parse(nullableType, propertyNewValue.ToString());
}
}
}
property.First().SetValue(businessEntity, propertyNewValue, null);
}
}
}
}
thisType = thisType.BaseType;
}
}
The strange thing is that when I get to the property setting sentence
property.First().SetValue(businessEntity, propertyNewValue, null);
propertyNewValue has a value of "091234" (as seen by debbuger)