How to set value of property where there is no setter

29.2k views Asked by At

I have seen various questions raised and answered where we can invoke a private setter using reflection such as this one:

Is it possible to get a property's private setter through reflection?

However I have some code which has a property i need to set but cant because there is no setter, I cant add a setter as this isn't my code. Is there a way to somehow set the value using reflection in this scenario?

5

There are 5 answers

9
Kyle On

You have to keep in mind that a property is just syntactic sugar for a pair of methods. One method (the getter) returns a value of the property type and one method (the setter) accepts a value of the property type.

There is no requirement that the getter and setter actually get or set anything. They're just methods, so they're allowed to do anything. The only requirement is that the getter return a value. From the outside there's no way you can really tell if there is a backing field. The getter could be getting computed every time it's called. It may be based on other properties.

So, no, there isn't really any way in general to "set" a property that doesn't have a setter.

0
Rubenisme On

Gleaning from the excellent answer above by @(Dan Solovay), we can now do something like this (made it easy to paste into LinqPad):

#nullable enable

void Main()
{
    var model = new MyModel();
    Console.WriteLine(model.Season);
    var setter = GetSetterForProperty<MyModel, SeasonEnum>(x => x.Season);
    setter?.Invoke(model, SeasonEnum.Summer);
    Console.WriteLine(model.Season);
}

enum SeasonEnum
{
    Unknown,
    Spring,
    Summer,
    Autumn,
    Winter
}

class MyModel
{
    public SeasonEnum Season { get; }
}

private const BindingFlags DeclaredOnlyLookup = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly;
private static Action<T, TValue>? GetSetterForProperty<T, TValue>(Expression<Func<T, TValue>> selector) where T : class
{
    var expression = selector.Body;
    var propertyInfo = expression.NodeType == ExpressionType.MemberAccess ? (PropertyInfo)((MemberExpression)expression).Member : null;

    if (propertyInfo is null)
    {
        return null;
    }

    var setter = GetPropertySetter(propertyInfo);

    return setter;

    static Action<T, TValue> GetPropertySetter(PropertyInfo prop)
    {
        var setter = prop.GetSetMethod(nonPublic: true);
        if (setter is not null)
        {
            return (obj, value) => setter.Invoke(obj, new object?[] { value });
        }

        var backingField = prop.DeclaringType?.GetField($"<{prop.Name}>k__BackingField", DeclaredOnlyLookup);
        if (backingField is null)
        {
            throw new InvalidOperationException($"Could not find a way to set {prop.DeclaringType?.FullName}.{prop.Name}. Try adding a private setter.");
        }

        return (obj, value) => backingField.SetValue(obj, value);
    }
}
0
Dan Solovay On

Adding a practical use case to @abyte0's answer.

Some libraries make use of reflection to set properties this way. For example, see this sample code from https://github.com/natemcmaster/CommandLineUtils:

using System;
using McMaster.Extensions.CommandLineUtils;

public class Program
{
    public static int Main(string[] args)
        => CommandLineApplication.Execute<Program>(args);

    [Option(Description = "The subject")]
    public string Subject { get; } = "world";

    [Option(ShortName = "n")]
    public int Count { get; } = 1;

    private void OnExecute()
    {
        for (var i = 0; i < Count; i++)
        {
            Console.WriteLine($"Hello {Subject}!");
        }
    }
}

Behind the scenes, this syntax is implemented with this code:

        public static SetPropertyDelegate GetPropertySetter(PropertyInfo prop)
        {
            var setter = prop.GetSetMethod(nonPublic: true);
            if (setter != null)
            {
                return (obj, value) => setter.Invoke(obj, new object?[] { value });
            }
            else
            {
                var backingField = prop.DeclaringType.GetField($"<{prop.Name}>k__BackingField", DeclaredOnlyLookup);
                if (backingField == null)
                {
                    throw new InvalidOperationException(
                        $"Could not find a way to set {prop.DeclaringType.FullName}.{prop.Name}. Try adding a private setter.");
                }

                return (obj, value) => backingField.SetValue(obj, value);
            }
        }

The practical value here is having the code express that the only way a value should be set is through a command line invocation. This is allowed: hello.exe -s world but this is not: Subject = "some other value";

1
Bruno Gozzi On

Could you use "propertyInfo" approach. See the example below:

With a class like this:

public class MyClass {
    public string MyAttribute{ get; } // --> blocked attribute

}

Use this code to change property value:

var instanceOfMyClass = new MyClass();

typeof(MyClass).GetProperty("MyAttribute")?.SetValue(instanceOfMyClass , "SomeValue");

or maybe you can write it a little more "elegant" using nameof.

typeof(MyClass).GetProperty(nameof(MyClass.MyAttribute))?.SetValue(instanceOfMyClass , "SomeValue");
4
Abyte0 On

I do not suggest doing this on your application but for testing purpose it may be usefull...

Assuming you have:

public class MyClass
{
     public int MyNumber {get;}
}

You could do this if its for test purpose, I would not suggest to use this in your runtime code:

var field = typeof(MyClass).GetField("<MyNumber>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic);
field.SetValue(anIstanceOfMyClass, 3);