Passando proprietà per riferimento in C #

Sto cercando di fare come segue:

GetString( inputString, ref Client.WorkPhone) private void GetString(string inValue, ref string outValue) { if (!string.IsNullOrEmpty(inValue)) { outValue = inValue; } } 

Questo mi sta dando un errore di compilazione. Penso che sia abbastanza chiaro quello che sto cercando di ottenere. Fondamentalmente voglio che GetString copi il contenuto di una stringa di input nella proprietà WorkPhone del Client .

È ansible passare una proprietà per riferimento?

Le proprietà non possono essere passate per riferimento. Ecco alcuni modi per aggirare questa limitazione.

1. Valore di ritorno

 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. Delegato

 void GetString(string input, Action 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. Espressione LINQ

 void GetString(string input, T target, Expression> 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. Riflessione

 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"); } 

senza duplicare la proprietà

 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 setter) { if (!string.IsNullOrEmpty(value)) { setter(value); } } 

Ho scritto un wrapper usando la variante ExpressionTree e il c # 7 (se qualcuno è interessato):

 public class Accessor { private Action Setter; private Func Getter; public Accessor(Expression> 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>(Expression.Call(instanceExpression, propertyInfo.GetSetMethod(), parameter), parameter).Compile(); Getter = Expression.Lambda>(Expression.Call(instanceExpression, propertyInfo.GetGetMethod())).Compile(); } else if (memberExpression.Member is FieldInfo fieldInfo) { Setter = Expression.Lambda>(Expression.Assign(memberExpression, parameter), parameter).Compile(); Getter = Expression.Lambda>(Expression.Field(instanceExpression,fieldInfo)).Compile(); } } public void Set(T value) => Setter(value); public T Get() => Getter(); } 

E usalo come:

 var accessor = new Accessor(() => myClient.WorkPhone); accessor.Set("12345"); Assert.Equal(accessor.Get(), "12345"); 

Un altro trucco non ancora menzionato è che la class che implementa una proprietà (es. Foo di tipo Bar ) definisce anche un delegato delegate void ActByRef(ref T1 p1, ref T2 p2); e implementare un metodo ActOnFoo(ref Bar it, ActByRef proc, ref TX1 extraParam1) (ed eventualmente anche versioni per due e tre “parametri extra”) che passeranno la sua rappresentazione interna di Foo al procedura fornita come parametro ref . Questo ha un paio di grandi vantaggi rispetto ad altri metodi di lavoro con la proprietà:

  1. La proprietà è aggiornata “sul posto”; se la proprietà è di un tipo compatibile con i metodi `Interlocked`, o se è una struct con campi esposti di tali tipi, i metodi` Interlocked` possono essere usati per eseguire aggiornamenti atomici alla proprietà.
  2. Se la proprietà è una struttura di campo esposto, i campi della struttura possono essere modificati senza doverne fare copie ridondanti.
  3. Se il metodo `ActByRef` passa uno o più parametri` ref` dal suo chiamante al delegato fornito, potrebbe essere ansible utilizzare un singleton o un delegato statico, evitando così la necessità di creare chiusure o delegati in fase di esecuzione.
  4. La proprietà sa quando viene “lavorato con”. Mentre è sempre necessario usare caucanvas eseguendo un codice esterno mentre si tiene un lucchetto, se uno può fidarsi che i chiamanti non facciano troppo nulla nel loro callback che potrebbe richiedere un altro blocco, può essere pratico avere il metodo a guardia dell’accesso della proprietà con un bloccare, in modo che gli aggiornamenti che non sono compatibili con `CompareExchange` possano ancora essere eseguiti quasi-atomicamente.

Passare le cose essere ref è un modello eccellente; peccato che non sia usato di più

Questo è trattato nella sezione 7.4.1 delle specifiche del linguaggio C #. Solo un riferimento di variabile può essere passato come parametro ref o out in un elenco di argomenti. Una proprietà non si qualifica come riferimento variabile e quindi non può essere utilizzata.

Non è ansible. Potresti dire

 Client.WorkPhone = GetString(inputString, Client.WorkPhone); 

dove WorkPhone è una proprietà string scrivibile e la definizione di GetString è cambiata in

 private string GetString(string input, string current) { if (!string.IsNullOrEmpty(input)) { return input; } return current; } 

Questo avrà la stessa semantica per cui sembra che tu stia cercando.

Questo non è ansible perché una proprietà è davvero un paio di metodi sotto mentite spoglie. Ogni proprietà rende disponibili getter e setter accessibili tramite syntax di tipo campo. Quando GetString a chiamare GetString come hai proposto, quello che stai passando è un valore e non una variabile. Il valore che stai passando è quello restituito dal get_WorkPhone .

Solo una piccola espansione per la soluzione Linq Expression di Nathan . Utilizzare un parametro multi generico in modo che la proprietà non si limiti alla stringa.

 void GetString(string input, TClass outObj, Expression> 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); } } } 

Quello che potresti provare a fare è creare un object per contenere il valore della proprietà. In questo modo potresti passare l’object e avere comunque accesso alla proprietà all’interno.

Se vuoi ottenere e impostare entrambe le proprietà, puoi usare questo in C # 7:

 GetString( inputString, (() => client.WorkPhone, x => client.WorkPhone = x)) void GetString(string inValue, (Func get, Action set) outValue) { if (!string.IsNullOrEmpty(outValue)) { outValue.set(inValue); } } 

Non è ansible effettuare il ref una proprietà, ma se le funzioni richiedono sia di get che di set accesso, è ansible passare un’istanza di una class con una proprietà definita:

 public class Property { 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; } } 

Esempio:

 class Client { private string workPhone; // this could still be a public property if desired public readonly Property WorkPhone; // this could be created outside Client if using a regular public property public int AreaCode { get; set; } public Client() { WorkPhone = new Property( delegate () { return workPhone; }, delegate (string value) { workPhone = value; }); } } class Usage { public void PrependAreaCode(Property phone, int areaCode) { phone.Value = areaCode.ToString() + "-" + phone.Value; } public void PrepareClientInfo(Client client) { PrependAreaCode(client.WorkPhone, client.AreaCode); } }