-1

I'm trying to generalize a C# class which holds multiple properties of the same type (and all of them have the same implementation as well).

What I want eventually is to remove all properties and hold a single Dictionary<string, type> which maps each property using a unique ID.

Removing the properties will be too much of an effort at the moment, but some of the existing functions can be refactored so that instead of multiple 'copy-paste` to read/update a property, use a loop over the future dictionary and update per key.

How can I do the following?

//Simplified example
class Person {

    public double Height { get; set; }
    public double Weight { get; set; }
    public double Age { get; set; }
    public double SomethingElse { get; set; }
    //.. maybe more

    public void CopyPasteCode()
    {
        Height = -1.0;
        Weight = -1.0;
        Age = -1.0;
        SomethingElse = -1.0;
    }

    public void Refactored()
    {
        var properties = //How to do this?
            new List<ref double>() 
            {
                ref Height, ref Weight, ref Age, ref IQ
            };
        foreach(var p in properties)
        {
            p = -1.0;
        }
    }
}
Palle Due
  • 5,929
  • 4
  • 17
  • 32
ZivS
  • 2,094
  • 2
  • 27
  • 48
  • 6
    This sounds like a bit of an XY problem. Are you really just trying to set all of your `double` properties to `-1`? – DavidG Aug 05 '19 at 08:43
  • You can achieve it using reflection, for example, e.g. `person.GetType().GetProperty(propertyName).GetValue(person, null);` – Pavel Anikhouski Aug 05 '19 at 08:44
  • Why would you put those properties in a list? They don't belong together just because their types are all `double`... How would a `loop` help when you still set by `key`? – Biesi Aug 05 '19 at 08:52
  • The real code is not a `double` rather an element that contains a unique id and a dynamic value. – ZivS Aug 05 '19 at 10:27

1 Answers1

1

I won't focus on why you might need such an approach, but rather show a couple of implementation techniques.

Option 1: Reflection, string property names

Using this helper method:

static void SetProperties(object obj, string[] propertyNames, object value)
{
    var type = obj.GetType();

    foreach (var name in propertyNames)
    {
        var property = type.GetProperty(name);
        property.SetValue(obj, value);        
    }
}

you do it like this:

public void Refactored()
{
    var propertyNames = new[] { 
       nameof(Height), nameof(Weight), nameof(Age), nameof(IQ) 
    };
    SetProperties(this, propertyNames, -0.1m);
}

Option 2: Reflection, strongly typed, properties specified with lambdas

The helper method:

static void SetProperties<TObj, TProp>(
    TObj obj, 
    TProp value, 
    params Expression<Func<TObj, TProp>>[] properties)
{
    foreach (var lambda in properties)
    {
        var property = (PropertyInfo)((MemberExpression)lambda.Body).Member;
        property.SetValue(obj, value);        
    }
}

and you use it as follows:

public void Refactored()
{
    SetProperties(
        this, 
        -0.1m,
        x => x.Height, x => x.Weight, x => x.Age, x => x.IQ);
}

Using lambdas has the advantage of compiler type- and name-checking, rename refactoring, and picking properties with IntelliSense (when you type x => x.).

Option 3: Reflection.Emit

Accessing properties through Reflection implies performance penalty, which can be affordable or not, depending on your requirements. A much faster way that provides same performance as the original CopyPasteCode(), is using dynamic methods built from IL instructions, which you create on the fly once and then use through the lifetime of the application.

However, if you're new to Reflection.Emit: it's a low-level mechanism, and it requires quite a bit of learning. For this reason, it would be much simpler and faster (and safer) to use one of available wrapper libraries. In this case, Marc Gravell's fast-member would be a good choice.

Combined with previous options:

static void SetProperties<TObj, TProp>(
    TObj obj, 
    TProp value, 
    params Expression<Func<TObj, TProp>>[] properties)
{
    var accessor = TypeAccessor.Create(obj.GetType()); 

    foreach (var lambda in properties)
    {
        var property = (PropertyInfo)((MemberExpression)lambda.Body).Member;
        accessor[obj, property.Name] = value;      
    }
}

the usage isn't changed:

public void Refactored()
{
    SetProperties(
        this, 
        -0.1m,
        x => x.Height, x => x.Weight, x => x.Age, x => x.IQ);
}
felix-b
  • 8,178
  • 1
  • 26
  • 36