Pregunta

Estoy intentando hacer lo siguiente:

GetString(
    inputString,
    ref Client.WorkPhone)

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

Esto me está dando un error de compilación.Creo que está bastante claro lo que estoy tratando de lograr.Básicamente quiero GetString para copiar el contenido de una cadena de entrada al WorkPhone propiedad de Client.

¿Es posible pasar una propiedad por referencia?

¿Fue útil?

Solución

Las propiedades no se pueden pasar por referencia. Aquí hay algunas maneras en que puede evitar esta limitación.

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. Expresión 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. Reflexión

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

Otros consejos

sin duplicar la propiedad

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

Escribí un contenedor usando la variante ExpressionTree y c # 7 (si alguien está interesado):

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

Y úsalo como:

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

Otro truco que aún no se menciona es que la clase que implementa una propiedad (por ejemplo, Foo de tipo Bar) también defina un delegado delegate void ActByRef<T1,T2>(ref T1 p1, ref T2 p2); e implemente un método ActOnFoo<TX1>(ref Bar it, ActByRef<Bar,TX1> proc, ref TX1 extraParam1) (y posiblemente versiones para dos y tres " parámetros adicionales " también) que pasarán su representación interna de ref al procedimiento suministrado como un parámetro <=>. Esto tiene un par de grandes ventajas sobre otros métodos de trabajo con la propiedad:

  1. La propiedad se actualiza " en el lugar " ;; si la propiedad es de un tipo que es compatible con los métodos 'Interbloqueados', o si es una estructura con campos expuestos de tales tipos, los métodos 'Interbloqueados' pueden usarse para realizar actualizaciones atómicas a la propiedad.
  2. Si la propiedad es una estructura de campo expuesto, los campos de la estructura pueden modificarse sin tener que hacer copias redundantes de la misma.
  3. Si el método `ActByRef` pasa uno o más parámetros` ref` a través de la persona que llama al delegado suministrado, puede ser posible usar un singleton o un delegado estático, evitando así la necesidad de crear cierres o delegados en tiempo de ejecución.
  4. La propiedad sabe cuándo se está " trabajado con " ;. Si bien siempre es necesario tener cuidado al ejecutar el código externo mientras se mantiene un bloqueo, si se puede confiar en que las personas que llaman no hagan nada en su devolución de llamada que pueda requerir otro bloqueo, puede ser práctico que el método proteja el acceso a la propiedad con un bloqueo, de modo que las actualizaciones que no son compatibles con `CompareExchange` aún podrían realizarse de forma cuasi-atómica.

Pasar cosas sea <=> es un patrón excelente; Lástima que no se use más.

Solo una pequeña expansión a solución de expresión Linq de Nathan . Utilice el parámetro multi genérico para que la propiedad no se limite a la cadena.

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

Si desea obtener y establecer ambas propiedades, puede usar esto en 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);
    }
}

Esto está cubierto en la sección 7.4.1 de la especificación del lenguaje C #. Solo se puede pasar una referencia de variable como parámetro ref o out en una lista de argumentos. Una propiedad no califica como referencia variable y, por lo tanto, no se puede usar.

Esto no es posible.Tu puedes decir

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

dónde WorkPhone es un escribible string propiedad y la definición de GetString se cambia a

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

Esto tendrá la misma semántica que parece estar intentando.

Esto no es posible porque una propiedad es en realidad un par de métodos disfrazados.Cada propiedad pone a disposición captadores y definidores a los que se puede acceder mediante una sintaxis similar a un campo.Cuando intentas llamar GetString Como has propuesto, lo que estás pasando es un valor y no una variable.El valor que está pasando es el devuelto por el captador. get_WorkPhone.

Lo que podría intentar hacer es crear un objeto para mantener el valor de la propiedad. De esa manera, podría pasar el objeto y aún tener acceso a la propiedad en su interior.

no puedes ref una propiedad, pero si sus funciones necesitan ambas get y set acceso puede pasar una instancia de una clase con una propiedad 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;
    }
}

Ejemplo:

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

¿Las propiedades no se pueden pasar por referencia? Conviértalo en un campo y use la propiedad para hacer referencia públicamente:

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 bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top