The goal of this code is to iterate through multiple nested classes, and multiple any integer by 2. Provided simple example, however, example will be more complicated in future.
How do I change a Object to its underlying class? When I iterate through this function, it reads the type for OuterProduct correctly, but fails for InnerProduct reading as type System.RuntimeType, giving an error below
How can I resolve this code to multiply all nested integers by 2?
An unhandled exception of type 'System.StackOverflowException' occurred in Unknown Module.
class Program
{
static void Main(string[] args)
{
var test = new OuterProduct();
test.AmountSold = 5;
test.ProductName = "BookOuter";
test.InnerProduct = new InnerProduct();
test.InnerProduct.ProductNameInner = "BookInner";
test.InnerProduct.AmountSoldInner = 7;
ReadPropertiesTest.ReadPropertiesRecursive(test);
}
}
public class OuterProduct
{
public string ProductName { get; set; }
public int AmountSold { get; set; }
public InnerProduct InnerProduct { get; set; }
}
public class InnerProduct
{
public string ProductNameInner { get; set; }
public int AmountSoldInner { get; set; }
}
public static class ReadPropertiesTest
{
public static void ReadPropertiesRecursive(object test)
{
var type = test.GetType();
foreach (PropertyInfo property in type.GetProperties())
{
if (property.PropertyType == typeof(int) || property.PropertyType == typeof(int?))
{
property.SetValue(test, (int)(property.GetValue(test)) * 2);
}
if (property.PropertyType.IsClass && !(property.PropertyType == typeof(string)))
{
ReadPropertiesRecursive(property.PropertyType);
}
}
}
}
Resources:
C#: How to get all public (both get and set) string properties of a type
How to iterate through nested properties of an object
System.RuntimeType is the implementation of the class that represents typeof(X) or something.GetType(). When you pass PropertyType to your function you are not passing the property value, but it's type.
You will need to pass the next object in the hierarchy into the recursive function by using GetValue.
Note though that this is dangerous and error prone. For example, if you have a List<> property you obviously cannot increase its Count (it is readonly!). You should check to make sure that the property can be written to using the CanWrite property.
You also need to check for null objects. On top of that we need to handle int differently from int? (otherwise casting null to int will throw). The latter we can clean up a bit with c#7 pattern matching:
public static void ReadPropertiesRecursive(object test)
{
if (test is null) // base case
return;
var type = test.GetType();
foreach (PropertyInfo property in type.GetProperties())
{
// check if we can even read the property
if(!property.CanRead)
continue;
// use pattern matching on the value
// nulls will be ignored
// we *could* cache GetValue but then it means we will invoke it for uninteresting types/properties
// it's also why I don't call GetValue until we've inspected PropertyType
if (property.CanWrite &&
(property.PropertyType == typeof(int) || property.PropertyType == typeof(int?)) &&
property.GetValue(test) is int i)
{
property.SetValue(test, i * 2);
}
else if (property.PropertyType.IsClass && property.PropertyType != typeof(string))
{
ReadPropertiesRecursive(property.GetValue(test));
}
}
}
An alternative version that omits some of the checks against PropertyType can also be used. It's a bit cleaner looking but it could potentially perform the GetValue reflection in cases where we don't need/want it (like on a double or a struct):
public static void ReadPropertiesRecursive(object test)
{
if (test is null) // base case
return;
var type = test.GetType();
foreach (PropertyInfo property in type.GetProperties())
{
// check if we can even read the property
if(!property.CanRead)
continue;
// possibly unnecessary if not int or class
var val = property.GetValue(test);
if (property.CanWrite && val is int i)
{
property.SetValue(test, i * 2);
}
else if (property.PropertyType.IsClass && property.PropertyType != typeof(string))
{
ReadPropertiesRecursive(val);
}
}
}
Note that you may want to have a whitelist or blacklist of types. Recursing into a Type object for example isn't going to get you much.
Alternative would be to go with more object-oriented approach. Make it responsibility of every class which need to be "updated".
For every type with properties which need to be updated introduce a method to do it.
public class OuterProduct
{
public string ProductName { get; set; }
public int AmountSold { get; set; }
public InnerProduct InnerProduct { get; set; }
public void Update()
{
AmountSold *= 2;
InnerProduct.Update();
}
}
public class InnerProduct
{
public string ProductNameInner { get; set; }
public int AmountSoldInner { get; set; }
public void Update()
{
AmountSoldInner *= 2;
}
}
// Usage is simple
var test = new OuterProduct
{
AmountSold = 5,
ProductName = "BookOuter",
InnerProduct = new InnerProduct
{
ProductNameInner = "BookInner",
AmountSoldInner = 7
}
};
test.Update();
// test.AmountSold == 10 is true
// test.InnerProduct.AmountSoldInner == 14 is true
This approach will simplify code maintenance. For example adding/removing properties or worse case scenario adding some other logic to Update method will be isolated in one class.
In your recursive call you are passing the type, not the actual property value:
if (property.PropertyType.IsClass && !(property.PropertyType == typeof(string)))
{
ReadPropertiesRecursive(property.PropertyType);
}
should be:
if (property.PropertyType.IsClass && !(property.PropertyType == typeof(string)))
{
ReadPropertiesRecursive(property.GetValue(test));
}
Related
I am doing unit testing, and basically want to check that the data that 2 objects hold is the same
Assert.AreEqual(object1, object2);
Assert.IsTrue(object1.Equals(object2)); //this of course doesn't work
I am searching for the C# equivalent of assertJ
Assert.That(object1).isEqualToComparingFieldByField(object2)
You could either use records (c# 9 +) or you have to override the Equals method (if you have access and you can change the objects that you're working with).
Records example:
var point = new Point(3, 4);
var point2 = new Point(3, 4);
var test = point.Equals(point2); //this is true
public record Point(int X, int Y);
with classes:
public class Point
{
public int X { get; }
public int Y { get; }
public override bool Equals(object? obj)
{
if (obj == null)
return false;
return obj is Point point && (point.X == X && point.Y == Y);
}
public override int GetHashCode()
{
return HashCode.Combine(X, Y);
}
}
if you are not allowed to touch the implementation, then you could use serialization and compare the strings:
var obj1Str = JsonConvert.SerializeObject(object1);
var obj2Str = JsonConvert.SerializeObject(object2);
Assert.Equal(obj1Str, obj2Str);
using Newtonsoft.Json nuget
C# classes are reference equality, which means that variables are the same using the standard Equals and == if they point to the same object, you could override that behaivour, but it may break something now or in the future.
Or, you could switch to using a construct that's value equality by default, which structs as well as record classes are. If you can't (or don't want to) do that you can implement a value equals "helper" method yourself. I would not recommend overriding the Equals method or the == operator, as that can (and most likely will) lead to errors in the future instead I recommend you write your own ValueEquals method or extension method, something along the lines of
class Foo
{
public int Count {get; set;}
public string Message {get; set;}
}
public static bool ValueEquals(this Foo self, Foo other)
{
return self.Count == other.Count && self.Message == other.Message;
}
public void MyTest()
{
// Arrange and Act
...
// Assert
Assert.IsTrue(myFoo1.ValueEquals(myFoo2));
}
Depending on whether or not you can/ want to add a ValueEquals to your Foo class you can decide on doing it with an extension method or a normal method.
You could also implement a IEqualityComparer<T> like
public class FooValueEqualityComparer : IEqualityComparer<Foo>
{
public bool Equals(Foo foo1, Foo foo2)
{
return foo1.Count == foo2.Count &&
foo1.Message == foo2.Message;
}
public int GetHashCode(Foo foo)
{
return foo.GetHashCode();
}
}
// Use it
public void MyTest()
{
// Arrange and Act
...
// Assert
Assert.IsTrue(new FooEqualityComparer().Equals(myFoo1, myFoo2));
}
Or, you could write a generic ValueEquals that works for all^* classes using Reflection:
public static class ValueEqualityComparer
{
public static bool ValueEquals<T>(this T self, T other) where T : class
{
var type = self.GetType();
if (type == typeof(string))
return self.Equals(other);
var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var property in properties)
{
var selfValue = property.GetValue(self);
var otherValue = property.GetValue(other);
// String is special, it's not primitive but is value equality like primitives
if (property.PropertyType.IsPrimitive || property.PropertyType == typeof(string))
{
if (!selfValue.Equals(otherValue))
return false;
}
// If the property is a List value equals each member
// Maybe find another type that allows indexing and is less restrictive
else if (typeof(IEnumerable).IsAssignableFrom(property.PropertyType))
{
var selfList = ((IEnumerable)property.GetValue(self)).Cast<object>();
var otherList = ((IEnumerable)property.GetValue(other)).Cast<object>();
try
{
// Using EquiZip from MoreLinq: https://github.com/morelinq/MoreLINQ/blob/master/MoreLinq/EquiZip.cs
foreach (var element in selfList.EquiZip(otherList, (selfItem, otherItem) => new { selfItem, otherItem }))
{
if (!ValueEquals(element.selfItem, element.otherItem))
return false;
}
}
catch (InvalidOperationException)
{
// MoreLINQ throws a InvalidOperationException if our two enumerables aren't the same length
return false;
}
}
else
{
if (!ValueEquals(selfValue, otherValue))
return false;
}
}
return true;
}
}
This implementation is by no means perfect, and should honestly only be used for UnitTests and also should be thoroughly tested itself. You can see my tests as a dotnetfiddle here
Or you could do it "dirty" and serialize the objects to a string and compare those values.
How to get value and type of "strx" at runtime? Cant get value of cell (properties) at runtime when using generics (as result of under code is "null").
Example
public class Foo
{
public int x, y;
public string strx, stry;
}
public void GetCellValueByName<T>(GridView gridview, string name/)
{
T = (T)Activator.CreateInstance(typeof(T));
object row = gridview.GetRow(gridview.GetSelectedRows()[0]);
if (row != null && row is T)
{
columnType = (T)gridview.GetRow(gridview.GetSelectedRows()[0]);
PropertyInfo info = columnType.GetType().GetProperty(name);
if (info != null)
{ // Here I got always null
info.GetValue(columnType, null);
}
}
}
string valueOfStrx = GetCellValueByName<Foo>(grid, "strx");
The problem is that in class Foo, strx is a field (member variable):
public string strx, stry;
In your method, you try to use GetProperty, but this will not find the field:
PropertyInfo info = columnType.GetType().GetProperty(name);
So either change the member to a property
public string strx { get; set; }
public string stry { get; set; }
or use GetField instead:
FieldInfo info = columnType.GetType().GetField(name);
// ...
info.GetValue(columnType); // Note that GetValue for a field does not take a second parameter
I'm working on a project and I need to be able to do the following:
Say, We have Aa class that is configuration class:
public class Aa
{
public Bb BbName { set; get; }
public string Dd { set; get; }
}
public class Bb
{
public string bb { set; get; }
public string cc { set; get; }
}
I want to fetch variables from environment in order to assign them to the respective properties. Here is what I mean: as a simulation of Environment.GetEnvironmentVariable, let's use Dictionary<string, string>.
Setup will look like this:
var dic = new Dictionary<string, string>();
dic.Add("Dd", "236.154");
dic.Add("BbName.bb", "value_for_Bb.bb"); // as you can see, I'm using nested object's full
dic.Add("BbName.cc", "value_for_Bb.cc"); // path, in order to be able to identify it
Then I wrote a method, which looks like this:
private static void ReadPropertiesRecursive<T>(T obj, Type type, List<string> prefixes)
{
foreach (PropertyInfo property in type.GetProperties())
{
if (property.PropertyType.GetTypeInfo().IsClass && property.PropertyType != typeof(string))
{
prefixes.Add(property.Name);
ReadPropertiesRecursive(obj, property.PropertyType, prefixes);
prefixes.Remove(property.Name);
}
else
{
var propertyFullName = prefixes != null && prefixes.Count > 0 ? $"{prefixes.Aggregate((i, j) => i + "." + j)}.{property.Name}" : property.Name;
property.SetValue(obj, dic[propertyFullName]);
Console.WriteLine(propertyFullName); // just for debugging
}
}
}
So, using recursion, I'm able to reach all nested objects of the given Type and the only thing that is left is assigning corresponding values to them. I'm trying to do that on property.SetValue(obj, dic[propertyFullName]);, but it throws an error, and it makes sense: obj is a "root" object, not the one that I want to assign values to, directly. So, I probably need to pass here not obj, but rather an instance of property, which is obj's property. Is that correct? Will that work? If yes, how should I do that?
That did the trick:
private static void ReadPropertiesRecursive<Tt>(Tt obj, Type type, List<string> prefixes)
{
foreach (PropertyInfo property in type.GetProperties())
{
if (property.PropertyType.GetTypeInfo().IsClass && property.PropertyType != typeof(string))
{
prefixes.Add(property.Name);
var val = property.GetValue(obj);
if (val == null)
val = Activator.CreateInstance(property.PropertyType);
property.SetValue(obj, val);
ReadPropertiesRecursive(val, property.PropertyType, prefixes);
prefixes.Remove(property.Name);
}
else
{
var propertyFullName = prefixes != null && prefixes.Count > 0 ? $"{prefixes.Aggregate((i, j) => i + "." + j)}.{property.Name}" : property.Name;
property.SetValue(obj, dic[propertyFullName]);
Console.WriteLine(propertyFullName);
}
}
}
I tried to find out all the "string" type properties from a class, but wonder how can I do that if there is any class type property in this class.
The following codes show the target class and my solution.
public class Credit_Card
{
public string brand { get; set; }
public string billing_phone { get; set; }
public Expiration expiration { get; set; }
}
public class Expiration
{
public string month { get; set; }
}
class program
{
static void Main(string[] args)
{
foreach (PropertyInfo prop in typeof(Credit_Card).GetProperties())
{
if(prop.PropertyType == typeof(string))
{
Console.WriteLine(prop.Name);
}
}
Console.ReadLine();
}
}
My "Main" method can only show "brand" and "billing_phone" properties in Credit_Card type, but missed "month" property in expiration class.
Is there any way that I can do recursive search in Credit_Card class?
You should be able to make a method that will recursively call itself, with the type to search as a parameter:
public void OutputStringProperties(Type type)
{
foreach (PropertyInfo prop in type.GetProperties())
{
if(prop.PropertyType == typeof(string))
{
Console.WriteLine(prop.Name);
}
else
{
OutputStringProperties(prop.PropertyType);
}
}
}
Then your initial call just invokes this with its starting point:
OutputStringProperties(typeof(Credit_Card));
However, bear in mind that this will cause a stack overflow if you have cyclic dependencies, unless you first modify it to keep track of which types it's already checked.
public static void Main (string[] args)
{
foreach(PropertyInfo prop in GetStringProperties(typeof(Credit_Card)))
Console.WriteLine(prop.Name);
Console.ReadLine();
}
public static IEnumerable<PropertyInfo> GetStringProperties(Type type)
{
return GetStringProperties (type, new HashSet<Type> ());
}
public static IEnumerable<PropertyInfo> GetStringProperties(Type type, HashSet<Type> alreadySeen)
{
foreach(var prop in type.GetProperties())
{
var propType = prop.PropertyType;
if (propType == typeof(string))
yield return prop;
else if(alreadySeen.Add(propType))
foreach(var indirectProp in GetStringProperties(propType, alreadySeen))
yield return indirectProp;
}
}
It's important to catch types we've already processed, or you can easily get into an infinite loop with the small mercy that since this takes a recursive approach it crashes with a StackOverflowException rather than just hanging forever like the iterative equivalent would. (Another case of the only thing worse than throwing an exception is no throwing an exception).
First I would like to apologize for the messy title. I'm not quite sure how to put it into words so I will describe the situation.
I'm writing a comparison engine for our product, that is capable of comparing different products like this:
public abstract class ComparableProduct
{
public ComparableProperty<decimal> Weight { get; set; }
public ComparableProperty<decimal> Width { get; set; }
public ComparableProperty<decimal> Height { get; set; }
public ComparableProperty<decimal> Depth { get; set; }
public bool IsBetterThan(ComparableProduct target){}
}
Actual products are derived from ComparableProduct, such as Screen : ComparableProduct, which adds property
ComparableProperty<decimal> Dimension { get; set; }
This means that I can have a class Laptop which has a property Keyboard, Screen, StorageDevice... etc etc, which are all derived from ComparableProduct.
Those have in turn comparable properties like this:
public abstract class ComparableProperty<T> where T : IComparable<T>
{
T Value { get; set; }
public ComparisonType ComparisonType { get; set; }
public bool IsBetterThan(T target)
{
if(ComparisonType == ComparisonType.GreaterThan)
return Value.CompareTo(target) >= 0;
return Value.CompareTo(target) <= 0;
}
public bool IsBetterThan(IEnumerable<T> targets)
{
foreach(var target in targets)
{
if (ComparisonType == ComparisonType.SmallerThan && Value.CompareTo(target) >= 0)
return false;
if (ComparisonType == ComparisonType.GreaterThan && Value.CompareTo(target) <= 0)
return false;
}
return true;
}
}
I haven't tested these, by all logic they should work. The trouble I'm having... is with the IsBetterThan method in ComparableProduct. The expected functionality is that on the top class (say, Laptop), is looped through its ComparableProduct properties and called IsBetterThan for the other copy and those will loop through their subproperties... In addition, all the ComparableProduct ComparableProperty properties are checked with IsBetterThan with the other class' equivalent value.
Anyway, here's what I have, and you can see the problem I have immediately.
public bool IsBetterThan(ComparableProduct target)
{
foreach(var property in GetType().GetProperties().Where(x => x.PropertyType == typeof(ComparableProduct)))
{
var compareTo = target.GetType().GetProperty(property.Name).GetValue(target, null) as ComparableProduct;
var local = property.GetValue(this, null) as ComparableProduct;
if(local != null && !local.IsBetterThan(compareTo))
return false;
}
foreach(var property in GetType().GetProperties().Where(x => x.PropertyType == typeof(ComparableProperty<>)))
{
var compareTo = target.GetType().GetProperty(property.Name).GetValue(target, null) as ComparableProperty<>;
var local = property.GetValue(this, null) as ComparableProperty<>;
if(local != null && !local.IsBetterThan(compareTo))
return false;
}
}
As you can see, I'm trying to cast it to ComparableProperty<>, which means it's missing the generic type. However, I'm not quite sure how to get the generic type of the involved property.
Also, if there's a better way of doing it... I will take any tips I can, but this is the first half decent way of doing it that came to my mind.
EDIT:
Spoke too soon. When I try to enumerate the properties in ComparableProduct's IsBetterThan like this:
foreach(var property in GetType().GetProperties())
{
var t = property.GetType().GetInterfaces();
if (!property.GetType().GetInterfaces().Contains(typeof(IComparableProperty))) continue;
var compareTo = target.GetType().GetProperty(property.Name).GetValue(target, null) as IComparableProperty;
var local = property.GetValue(this, null) as IComparableProperty;
if (local == null) continue;
if(!local.IsBetterThan(compareTo))
return false;
}
Then it appears it can't find IComparableProperty in the interfaces. I have gone through the main methods that may contain it... but the only interfaces t contains are ICustomAttributeProvider, _MemberInfo, _PropertyInfo and ISerializable.
EDIT 2:
I have solved this by falling back on string comparison on
if (property.PropertyType.Name != "ComparableProperty`1") continue;
And after changing T to ComparableProperty and IEnumerable to IEnumerable> the whole comparison works perfectly.
You can create a non-generic interface, and then work with it:
public interface IComparableProperty
{
bool IsBetterThan(object target);
bool IsBetterThan(IEnumerable targets);
}
public abstract class ComparableProperty<T>: IComparableProperty where T : IComparable<T>
{
T Value { get; set; }
public ComparisonType ComparisonType { get; set; }
public bool IsBetterThan(T target)
{
if (ComparisonType == ComparisonType.GreaterThan)
return Value.CompareTo(target) >= 0;
return Value.CompareTo(target) <= 0;
}
public bool IsBetterThan(IEnumerable<T> targets)
{
foreach (var target in targets)
{
if (ComparisonType == ComparisonType.SmallerThan && Value.CompareTo(target) >= 0)
return false;
if (ComparisonType == ComparisonType.GreaterThan && Value.CompareTo(target) <= 0)
return false;
}
return true;
}
bool IComparableProperty.IsBetterThan(object target)
{
return IsBetterThan((T) target);
}
bool IComparableProperty.IsBetterThan(IEnumerable targets)
{
return IsBetterThan((IEnumerable<T>) (targets));
}
}
EDIT 0:
Did you try to use this method Type.IsAssignableFrom(Type) like this:
foreach (var property in GetType().GetProperties())
{
if (!typeof(IComparableProperty).IsAssignableFrom(property.PropertyType)) continue;
var compareTo = target.GetType().GetProperty(property.Name).GetValue(target, null) as IComparableProperty;
var local = property.GetValue(this, null) as IComparableProperty;
if (local == null) continue;
return local.IsBetterThan(compareTo);
}