Creating an object with a subset of properties and their values from another object

2.1k Views Asked by At

Consider the following class -

public class User
{
    [Selected]
    public string Name { get; set; }
    public string Email { get; set; }
    public string Password { get; set; }
    [Selected]
    public int Code { get; set; }
    
    public object GetValue()
    {
        // need to do something here
    }
}

[Selected] here is nothing but a marker attribute. I want GetValue method to return an object which will have the [Selected]-marked properties with corresponding values. That is, in the code below -

private static void Execute()
{
    User user = new User
    {
        Name = "alice",
        Email = "[email protected]",
        Password = "123456",
        Code = 1234
    };

    var value = user.GetValue();
}

value should be an object with two properties Name and Code which should have the values "alice" and 1234 respectively.

After some searching I tried ExpandoObject (which I never used before) -

public object GetValue()
{
    var dictionary = this.GetType().GetProperties().Where(p => p.GetCustomAttribute(typeof(Selected), false) != null).ToDictionary(p => p.Name);
    dynamic expando = new ExpandoObject();
    foreach (var item in dictionary)
    {
        object value = item.Value.GetValue(this);
        ((IDictionary<string, object>)expando).Add(item.Key, value);
    }
    return expando;
}

But it didn't serve my purpose - the client/consumer of value object somehow couldn't read/access the property values.

Any suggestions?

Edit :
There might be a lot of classes like User and the GetValue method will be called from within a generic method. So, at runtime I have no way to know what type the object is and which properties are marked.

3

There are 3 best solutions below

1
On

What you did works well, you just have to use the keyword dynamic when you call the method GetValue.

    dynamic value = user.GetValue();

If you are using var, value will be of the same type as the return type of your function (i.e object) at compile time. Therefore, if you try to do value.Name your compiler won't allow it because the class Object doesn't have any attribute Name.

dynamic tells your program to do the type checking at runtime.

0
On

To access the fields by name it is easier to cast the returned object to IDictionary:

    var value = (IDictionary<string, object>) user.GetValue();
    Console.WriteLine(value["Name"]);
2
On

Simplify your method to this:

public Dictionary<string, object> GetValue()
{
    var dictionary = this.GetType()
        .GetProperties()
        .Where(p => p.GetCustomAttribute(typeof(Selected), false) != null)
        .ToDictionary(p => p.Name, p => p.GetValue(this));

    return dictionary;
}

Use:

var value = user.GetValue(); // value is Dictionary

foreach (var kvp in value)
{
    Console.WriteLine(kvp);
}

If you wish POCO, then you can do it like follows

public class User
{
    public string Name { get; set; }
    public string Email { get; set; }
    public string Password { get; set; }
    public int Code { get; set; }

    public SelectedUserProperties GetValue()
    {
        return new SelectedUserProperties
        {
            Name = Name,
            Code = Code
        };
    }
}

public class SelectedUserProperties
{
    public string Name { get; set; }
    public int Code { get; set; }
}

It is assumed that the selected properties are known in advance, before compilation.
This makes the marker attribute unnecessary and can be completely removed.