Domanda

Sto provando a fare quanto 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 cosa sto cercando di ottenere. Fondamentalmente voglio GetString copiare il contenuto di una stringa di input nella proprietà WorkPhone di Client.

È possibile passare una proprietà per riferimento?

È stato utile?

Soluzione

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

1. Valore restituito

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

Altri suggerimenti

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

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

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 usalo come:

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

Un altro trucco non ancora menzionato è avere la classe che implementa una proprietà (ad es. Foo di tipo Bar) anche definire un delegato delegate void ActByRef<T1,T2>(ref T1 p1, ref T2 p2); e implementare un metodo ActOnFoo<TX1>(ref Bar it, ActByRef<Bar,TX1> proc, ref TX1 extraParam1) (e possibilmente versioni per due e tre " parametri aggiuntivi " pure) che passerà la sua rappresentazione interna di ref alla procedura fornita come <=> parametro. Questo ha un paio di grandi vantaggi rispetto ad altri metodi di lavoro con la proprietà:

  1. La proprietà viene aggiornata " in posizione " ;; se la proprietà è di un tipo compatibile con i metodi `Interlocked` o se è una struttura con campi esposti di tali tipi, i metodi` Interlocked` possono essere usati per eseguire aggiornamenti atomici sulla proprietà.
  2. Se la proprietà è una struttura di campi esposti, 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 possibile usare un delegato singleton o statico, evitando così la necessità di creare chiusure o delegati in fase di esecuzione.
  4. La proprietà sa quando è " ha funzionato con " ;. Mentre è sempre necessario usare cautela nell'esecuzione di un codice esterno mentre si tiene un blocco, se si può fidarsi dei chiamanti di non fare troppo nel loro callback che potrebbe richiedere un altro blocco, può essere pratico avere il metodo di proteggere l'accesso alla proprietà con un lock, in modo tale che gli aggiornamenti che non sono compatibili con `CompareExchange` possano ancora essere eseguiti quasi atomicamente.

Passare le cose <=> è un modello eccellente; peccato che non sia più utilizzato.

Solo una piccola espansione a la soluzione Linq Expression di Nathan . Utilizzare parametri multi generici in modo che la proprietà non si limiti alla stringa.

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 si desidera ottenere e impostare entrambe le proprietà, è possibile utilizzarlo in 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);
    }
}

Questo è trattato nella sezione 7.4.1 delle specifiche del linguaggio C #. Solo un riferimento a 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.

Questo non è possibile. 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;
}

Questa avrà la stessa semantica per cui sembra che tu stia provando.

Questo non è possibile perché una proprietà è in realtà una coppia di metodi mascherati. Ogni proprietà rende disponibili getter e setter accessibili tramite una sintassi simile a un campo. Quando tenti di chiamare get_WorkPhone come da te proposto, ciò che stai passando è un valore e non una variabile. Il valore che stai passando è quello restituito dal getter <=>.

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

Non puoi ref una proprietà, ma se le tue funzioni richiedono sia l'accesso get sia set puoi passare un'istanza di una classe con una proprietà definita:

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

Esempio:

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

Le proprietà non possono essere passate per riferimento? Renderlo quindi un campo e utilizzare la proprietà per fare riferimento a esso pubblicamente:

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
    }
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top