Pergunta

Eu estou tentando fazer 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 compilação. Eu acho que é bastante claro o que estou tentando alcançar. Basicamente eu quero GetString para copiar o conteúdo de uma cadeia de entrada para a propriedade WorkPhone de Client.

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

Foi útil?

Solução

Propriedades não pode ser passado por referência. Aqui estão algumas maneiras que você pode 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. Delegar

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 Expression

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

Outras dicas

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

Eu escrevi um wrapper utilizando a variante ExpressionTree e c # 7 (se alguém está interessado):

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

E usá-lo como:

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

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

  1. A propriedade é atualizado "no lugar"; se a propriedade é de um tipo que é compatível com `métodos Interlocked`, ou se é uma estrutura com campos expostos de tais tipos, os` métodos Interlocked` pode ser usado para executar atualizações atômicas ao referido imóvel.
  2. Se a propriedade é uma estrutura de campo exposto, os campos da estrutura pode ser modificado sem ter que fazer quaisquer cópias redundantes do mesmo.
  3. Se o método `ActByRef` passa um ou mais parâmetros` ref` através da sua chamada para o delegado fornecido, pode ser possível usar uma única ou delegado estático, evitando assim a necessidade de criar ou fechos delegados na run-time.
  4. A propriedade sabe quando ele está sendo "trabalhado com". Embora seja sempre necessário usar o cuidado de executar código externo mantendo um bloqueio, se se pode confiar chamadores não fazer muito fazer qualquer coisa em seu retorno que pode exigir outro bloqueio, pode ser prático ter o guarda método de acesso propriedade com um bloqueio, de modo que as atualizações que não são compatíveis com `CompareExchange` ainda pode ser realizada quase-atomicamente.

Passando coisas sejam ref é um excelente padrão; muito ruim não é mais utilizado.

Apenas uma pequena expansão para de Nathan Linq solução Expression . Use vários param genérico de forma que a propriedade não se limitando a string.

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

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

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

Isto não é possível. Pode-se dizer

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

onde WorkPhone é uma propriedade string gravável ea definição de GetString é alterado para

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

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

Isso não é possível porque a propriedade é realmente um par de métodos disfarçados. Cada propriedade faz getters e setters que são acessíveis via sintaxe campo-like disponíveis. Quando você tenta chamar GetString como você já proposto, o que você está passando é um valor e não uma variável. O valor que você está passando é que voltou do get_WorkPhone getter.

O que você pode tentar fazer é criar um objeto para manter o valor da propriedade. Dessa forma, você pode passar o objeto e ainda ter acesso ao interior da propriedade.

Você não pode ref uma propriedade, mas se suas funções precisa tanto get e acesso set você pode passar em torno de uma instância de uma classe com uma propriedade definida:

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

Exemplo:

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

Propriedades não pode ser passado por referência? Torná-lo um campo de então, e usar a propriedade para referenciá-lo publicamente:

public class MyClass
{
    public class MyStuff
    {
        string foo { get; set; }
    }

    private ObservableCollection<MyStuff> _collection;

    public ObservableCollection<MyStuff> Items { get { return _collection; } }

    public MyClass()
    {
        _collection = new ObservableCollection<MyStuff>();
        this.LoadMyCollectionByRef<MyStuff>(ref _collection);
    }

    public void LoadMyCollectionByRef<T>(ref ObservableCollection<T> objects_collection)
    {
        // Load refered collection
    }
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top