Передача свойств по ссылке в C#
Я пытаюсь сделать следующее:
GetString(
inputString,
ref Client.WorkPhone)
private void GetString(string inValue, ref string outValue)
{
if (!string.IsNullOrEmpty(inValue))
{
outValue = inValue;
}
}
это дает мне ошибку при компиляции. Я думаю, что это довольно ясно, что я пытаюсь достичь. В основном я хочу!--1--> скопировать содержимое входной строки WorkPhone собственность Client.
можно ли передать свойство по ссылке?
10 ответов:
свойства не могут быть переданы по ссылке. Вот несколько способов обойти это ограничение.
1. Возвращаемое Значение
string GetString(string input, string output) { if (!string.IsNullOrEmpty(input)) { return input; } return output; } void Main() { var person = new Person(); person.Name = GetString("test", person.Name); Debug.Assert(person.Name == "test"); }2. Делегат
void GetString(string input, Action<string> setOutput) { if (!string.IsNullOrEmpty(input)) { setOutput(input); } } void Main() { var person = new Person(); GetString("test", value => person.Name = value); Debug.Assert(person.Name == "test"); }3. Выражение LINQ
void GetString<T>(string input, T target, Expression<Func<T, string>> outExpr) { if (!string.IsNullOrEmpty(input)) { var expr = (MemberExpression) outExpr.Body; var prop = (PropertyInfo) expr.Member; prop.SetValue(target, input, null); } } void Main() { var person = new Person(); GetString("test", person, x => x.Name); Debug.Assert(person.Name == "test"); }4. Отражение
void GetString(string input, object target, string propertyName) { if (!string.IsNullOrEmpty(input)) { prop = target.GetType().GetProperty(propertyName); prop.SetValue(target, input); } } void Main() { var person = new Person(); GetString("test", person, nameof(Person.Name)); Debug.Assert(person.Name == "test"); }
без дублирования в собственность
void Main() { var client = new Client(); NullSafeSet("test", s => client.Name = s); Debug.Assert(person.Name == "test"); NullSafeSet("", s => client.Name = s); Debug.Assert(person.Name == "test"); NullSafeSet(null, s => client.Name = s); Debug.Assert(person.Name == "test"); } void NullSafeSet(string value, Action<string> setter) { if (!string.IsNullOrEmpty(value)) { setter(value); } }
Я написал обертку, используя вариант ExpressionTree и c#7 (Если кто-то заинтересован):
public class Accessor<T> { private Action<T> Setter; private Func<T> Getter; public Accessor(Expression<Func<T>> expr) { var memberExpression = (MemberExpression)expr.Body; var instanceExpression = memberExpression.Expression; var parameter = Expression.Parameter(typeof(T)); if (memberExpression.Member is PropertyInfo propertyInfo) { Setter = Expression.Lambda<Action<T>>(Expression.Call(instanceExpression, propertyInfo.GetSetMethod(), parameter), parameter).Compile(); Getter = Expression.Lambda<Func<T>>(Expression.Call(instanceExpression, propertyInfo.GetGetMethod())).Compile(); } else if (memberExpression.Member is FieldInfo fieldInfo) { Setter = Expression.Lambda<Action<T>>(Expression.Assign(memberExpression, parameter), parameter).Compile(); Getter = Expression.Lambda<Func<T>>(Expression.Field(instanceExpression,fieldInfo)).Compile(); } } public void Set(T value) => Setter(value); public T Get() => Getter(); }и использовать его как:
var accessor = new Accessor<string>(() => myClient.WorkPhone); accessor.Set("12345"); Assert.Equal(accessor.Get(), "12345");
еще один трюк, который еще не упомянут, - это класс, который реализует свойство (например,
FooтипаBar) также определить делегатdelegate void ActByRef<T1,T2>(ref T1 p1, ref T2 p2);и реализовать методActOnFoo<TX1>(ref Bar it, ActByRef<Bar,TX1> proc, ref TX1 extraParam1)(и, возможно, версии для двух и трех "дополнительных параметров"), которые передадут его внутреннее представлениеFooк поставленной процедуре как
это описано в разделе 7.4.1 спецификации языка C#. В качестве параметра ref или out в списке аргументов может быть передана только ссылка на переменную. Свойство не квалифицируется как ссылка на переменную и, следовательно, не может быть использовано.
это невозможно. Можно сказать
Client.WorkPhone = GetString(inputString, Client.WorkPhone);здесь
WorkPhoneв записиstringсвойства и определениеGetStringизменено наprivate string GetString(string input, string current) { if (!string.IsNullOrEmpty(input)) { return input; } return current; }это будет иметь ту же семантику, что вы, кажется, пытались.
это невозможно, потому что свойство-это действительно пара замаскированных методов. Каждое свойство делает доступными геттеры и сеттеры, которые доступны через синтаксис, подобный полю. При попытке позвонить
GetStringкак вы предполагается, что вы передаете значение, а не переменную. Значение, которое вы передаете в том, что возвращается из геттераget_WorkPhone.
просто немного расширения до решение выражения Linq Натана. С помощью Multi универсальный парам так, что собственность не ограничивается строкой.
void GetString<TClass, TProperty>(string input, TClass outObj, Expression<Func<TClass, TProperty>> outExpr) { if (!string.IsNullOrEmpty(input)) { var expr = (MemberExpression) outExpr.Body; var prop = (PropertyInfo) expr.Member; if (!prop.GetValue(outObj).Equals(input)) { prop.SetValue(outObj, input, null); } } }
Что вы можете попробовать сделать, это создать объект для хранения значения свойства. Таким образом, вы можете передать объект и по-прежнему иметь доступ к свойству внутри.
Если вы хотите получить и установить имущество, вы можете использовать это в C#7:
GetString( inputString, (() => client.WorkPhone, x => client.WorkPhone = x)) void GetString(string inValue, (Func<string> get, Action<string> set) outValue) { if (!string.IsNullOrEmpty(outValue)) { outValue.set(inValue); } }
нельзя
refсвойство, но если ваши функции нуждаются в обоихgetиsetдоступ вы можете передать экземпляр класса с определенным свойством:public class Property<T> { public delegate T Get(); public delegate void Set(T value); private Get get; private Set set; public T Value { get { return get(); } set { set(value); } } public Property(Get get, Set set) { this.get = get; this.set = set; } }пример:
class Client { private string workPhone; // this could still be a public property if desired public readonly Property<string> WorkPhone; // this could be created outside Client if using a regular public property public int AreaCode { get; set; } public Client() { WorkPhone = new Property<string>( delegate () { return workPhone; }, delegate (string value) { workPhone = value; }); } } class Usage { public void PrependAreaCode(Property<string> phone, int areaCode) { phone.Value = areaCode.ToString() + "-" + phone.Value; } public void PrepareClientInfo(Client client) { PrependAreaCode(client.WorkPhone, client.AreaCode); } }
Comments