Search code examples
c#system.reflectionsortedsetpropertyinfo

C# PropertyInfo.GetValue() when the property is a SortedSet<T>


I refactored the code and added comments to make it clear. See pic of code and result

UPDATE: I know the info I provided was a bit flawed so I edited the question. Nevertheless, how does one get the value from the property when it's a SortedSet? When I try to iterate over the return from GetValue() it gives me an Argument Null Exception.

Is there a practical way to get the PropertyInfo.Name and PropertyInfo.GetValue() from a class instance when said property is a SortedSet<T>? For example:

The machine class looks like this:

public class Machine : IComparable, IComparable<Machine>
{
    [BsonId]
    public Guid Id { get; set; }

    [BsonElement]
    public SortedSet<SubAssembly> SubAssemblies { get; set; } = [];
}

I then pull all of the Machines from a DB and try to get the property names and values like so:

 foreach (var MachineInDB in db.GetRecordList<Machine>(MachinesTable))
 {
     PropertyInfo[] MachineClassProperties = MachineInDB.GetType().GetProperties();

     Console.WriteLine("\n");

     foreach (PropertyInfo MachineProperty in MachineClassProperties)
     {
         
          Console.WriteLine($"{MachineProperty.Name} => {MachineProperty.GetValue(MachineInDB)}");
            
          //this one will print the following when it gets to the problem property
          //SubAssemblies => System.Collections.Generic.SortedSet`1[SubAssembly]

          //If I try to iterate over the SortedSet property, I get an ArgumentNull exception. 
         
     }
 }

Every other property in the class prints fine with the above code. If I print each Machine from the DB using ToString() all of the SubAssemblies print perfectly, so no problem pulling the data from the database.

Somehow the PropertyInfo element is null when it try to use reflectance only on the 'SortedSet'. Does anyone know how to work around that? I bet that if I switch to a List<T> it would work fine but I really need it to be a SortedSet<T>.

I've also tried putting the return value into a 'var' and iterating over it but I end up with an ArgumentNull exception. This is how I know it's just not pulling the object with the GetValue() method.

Is it wrong to expect a 'SortedSet' object? What's the right way to do this?

Thank you!

BMET1


Solution

  • In your screenshot example, you're passing an object to string.Join and expecting it to figure out that it is a collection and figure out which properties to output. It won't do that.

    If you're explicit about what you want, e.g. by telling the compiler how to treat the object and which properties to output, there is no issue retrieving the SortedSet via Reflection.

    If you don't want to cast anything, you can use Reflection on the item that is retrieved from the collection and iterate over its properties too.

    public class SubAssembly : IComparable
    {
        public string Name { get; set; }
        public int CompareTo(SubAssembly other) => Name.CompareTo(other.Name);
        public int CompareTo(object other) => Name.CompareTo((other as SubAssembly)?.Name);
    }
    
    public class Machine 
    {
        public Guid Id { get; set; }
    
        public SortedSet<SubAssembly> SubAssemblies { get; set; } = new SortedSet<SubAssembly>();
    }
    
    public static void Main()
    {
        var machine = new Machine
        {
            SubAssemblies = 
            {
                new SubAssembly
                {
                    Name = "Foo"
                },
                new SubAssembly
                {
                    Name = "Bar"
                }
            }
        };
        var propertyInfo = machine.GetType().GetProperty("SubAssemblies");
        var subAssemblies = propertyInfo.GetValue(machine);
        
        Console.WriteLine("This will contain a type string: " + string.Join(",", subAssemblies));
        Console.WriteLine("Here's your data: " + string.Join(",", ((SortedSet<SubAssembly>)subAssemblies).Select( x => x.Name)));
    
        //In case you don't want to cast anything
        foreach (var subAssemblyObject in subAssemblies as IEnumerable)
        {
            var type = subAssemblyObject.GetType();
            Console.WriteLine(string.Join(",", type.GetProperties(BindingFlags.Public | BindingFlags.Instance).Select( x => x.Name + ": " + x.GetValue(subAssemblyObject))));
        }
    }
    

    Output:

    This will contain a type string: System.Collections.Generic.SortedSet`1[SubAssembly]
    Here's your data: Bar,Foo
    Name: Bar
    Name: Foo
    

    See DotNetFiddle for working example.