Как установить значение свойства там, где нет установщика
Я видел различные вопросы и отвечал на них, где мы можем вызвать частный сеттер, используя рефлексию, такую как эта:
Можно ли получить частный установщик свойства через отражение?
Однако у меня есть некоторый код, который имеет свойство, которое мне нужно установить, но не могу, потому что нет установщика, я не могу добавить установщик, так как это не мой код. Есть ли способ как-то установить значение, используя отражение в этом сценарии?
5 ответов
Я не предлагаю делать это в вашем приложении, но для целей тестирования это может быть полезно...
Предполагая, что у вас есть:
public class MyClass
{
public int MyNumber {get;}
}
Вы можете сделать это, если это для целей тестирования, я бы не советовал использовать это в вашем коде времени выполнения:
var field = typeof(MyClass).GetField("<MyNumber>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic);
field.SetValue(anIstanceOfMyClass, 3);
Вы должны иметь в виду, что свойство является просто синтаксическим сахаром для пары методов. Один метод (метод получения) возвращает значение типа свойства, а один метод (метод установки) принимает значение типа свойства.
Не требуется, чтобы получатель и установщик действительно получали или устанавливали что-либо. Они просто методы, поэтому им разрешено делать все что угодно. Единственное требование - чтобы получатель возвращал значение. Со стороны нет никакого способа, которым вы действительно можете сказать, есть ли поле поддержки. Получатель может вычисляться каждый раз, когда вызывается. Это может быть основано на других свойствах.
Так что нет, вообще-то нет никакого способа "установить" свойство, для которого нет установщика.
Основываясь на превосходном ответе @(Dan Solovay) выше, теперь мы можем сделать что-то вроде этого (упростили вставку в 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);
}
}
Добавление практического примера использования к Abyte0 .
Некоторые библиотеки используют отражение для установки свойств таким образом. Например, см. Этот образец кода с 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}!");
}
}
}
За кулисами этот синтаксис реализован с помощью этого кода :
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);
}
}
Практическая ценность здесь заключается в том, что код выражает, что единственный способ установить значение - это вызов командной строки. Это разрешено:
hello.exe -s world
но это не так:
Subject = "some other value";
Не могли бы вы использовать подход «propertyInfo». См. пример ниже:
С таким классом:
public class MyClass {
public string MyAttribute{ get; } // --> blocked attribute
}
Используйте этот код для изменения значения свойства:
var instanceOfMyClass = new MyClass();
typeof(MyClass).GetProperty("MyAttribute")?.SetValue(instanceOfMyClass , "SomeValue");
или, может быть, вы можете написать это немного более «элегантно», используя nameof.
typeof(MyClass).GetProperty(nameof(MyClass.MyAttribute))?.SetValue(instanceOfMyClass , "SomeValue");