Passando propriedades por referência em c #

Eu estou tentando fazer o seguinte:

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

Isso está me dando um erro de compilation. Eu acho bem claro o que estou tentando alcançar. Basicamente eu quero que o GetString copie o conteúdo de uma string de input para a propriedade WorkPhone do Client .

É possível passar uma propriedade por referência?

Propriedades não podem ser passadas por referência. Aqui estão algumas maneiras de contornar essa limitação.

1. Valor de Retorno

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

 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. Expressão 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. Reflexão

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

sem duplicar a propriedade

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

Eu escrevi um wrapper usando a variante ExpressionTree e c # 7 (se alguém estiver interessado):

 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 usá-lo como:

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

Outro truque ainda não mencionado é ter a class que implementa uma propriedade (por exemplo, Foo do tipo Bar ) também definir um delegate delegate void ActByRef(ref T1 p1, ref T2 p2); e implementar um método ActOnFoo(ref Bar it, ActByRef proc, ref TX1 extraParam1) (e possivelmente versões para dois e três “parâmetros extras” também) que passará sua representação interna de Foo para o procedimento fornecido como um parâmetro ref . Isso tem algumas grandes vantagens sobre outros methods de trabalho com a propriedade:

  1. A propriedade é atualizada “in place”; se a propriedade for de um tipo compatível com methods `Interlocked`, ou se for uma struct com campos expostos de tais tipos, os methods` Interlocked` podem ser usados ​​para executar atualizações atômicas na propriedade.
  2. Se a propriedade for uma estrutura de campo exposto, os campos da estrutura poderão ser modificados sem a necessidade de fazer cópias redundantes dela.
  3. Se o método `ActByRef` passar um ou mais parâmetros` ref` através de seu responsável pela chamada para o delegado fornecido, pode ser possível usar um delegado singleton ou static, evitando a necessidade de criar closures ou delegates em tempo de execução.
  4. A propriedade sabe quando está sendo “trabalhada”. Embora seja sempre necessário ter cuidado ao executar código externo enquanto mantém um bloqueio, se alguém puder confiar que os chamadores não façam nada em seu retorno de chamada que possa exigir outro bloqueio, pode ser prático que o método proteja o access à propriedade com um lock, de modo que as atualizações que não são compatíveis com o `CompareExchange` ainda possam ser executadas quase atomicamente.

Passar as coisas seja ref é um excelente padrão; muito ruim não é mais usado.

Isso é abordado na seção 7.4.1 da especificação de linguagem C #. Somente uma referência de variável pode ser passada como um parâmetro ref ou out em uma lista de argumentos. Uma propriedade não se qualifica como uma referência de variável e, portanto, não pode ser usada.

Isso não é possível. Você poderia dizer

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

onde WorkPhone é uma propriedade de string gravável e a definição de GetString é alterada para

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

Isto terá a mesma semântica que você parece estar tentando.

Isso não é possível porque uma propriedade é realmente um par de methods disfarçados. Cada propriedade disponibiliza getters e setters acessíveis por meio de syntax semelhante a um campo. Quando você tenta chamar GetString como você propôs, o que você está passando é um valor e não uma variável. O valor que você está passando é retornado do get_WorkPhone getter.

Apenas uma pequena expansão para a solução de Expressão Linq de Nathan . Use multi parameter genérico para que a propriedade não se limite a string.

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

O que você poderia tentar fazer é criar um object para manter o valor da propriedade. Dessa forma você poderia passar o object e ainda ter access à propriedade dentro.

Se você deseja obter e definir a propriedade, você pode usar isso em 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); } } 

Você não pode ref uma propriedade, mas se suas funções precisarem de access get e set você poderá passar uma instância de uma class com uma propriedade definida:

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

Exemplo:

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