web-dev-qa-db-fra.com

Passage de propriétés par référence en C #

J'essaie de faire ce qui suit:

GetString(
    inputString,
    ref Client.WorkPhone)

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

Cela me donne une erreur de compilation. Je pense que ce que j'essaie de faire est très clair. En gros, je veux que GetString copie le contenu d'une chaîne d'entrée dans la propriété WorkPhone de Client.

Est-il possible de transmettre une propriété par référence?

189
yogibear

Les propriétés ne peuvent pas être transmises par référence. Voici quelques façons de contourner cette limitation.

1. Valeur de retour

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. délégué

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. Expression 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. réflexion

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

sans dupliquer la propriété

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

J'ai écrit un wrapper utilisant la variante ExpressionTree et le c # 7 (si quelqu'un est intéressé):

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

Et utilisez-le comme:

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

Une autre astuce non encore mentionnée consiste à ce que la classe qui implémente une propriété (par exemple Foo de type Bar) définisse également un délégué delegate void ActByRef<T1,T2>(ref T1 p1, ref T2 p2); et implémente une méthode ActOnFoo<TX1>(ref Bar it, ActByRef<Bar,TX1> proc, ref TX1 extraParam1) (et éventuellement des versions pour deux et trois "paramètres supplémentaires") représentation interne de Foo à la procédure fournie sous la forme d'un paramètre ref. Cela présente quelques gros avantages par rapport aux autres méthodes de travail avec la propriété:

  1. La propriété est mise à jour "en place"; si la propriété est d'un type compatible avec les méthodes `Interlocked`, ou s'il s'agit d'une structure avec des champs exposés de ce type, les méthodes` Interlocked` peuvent être utilisées pour effectuer des mises à jour atomiques de la propriété .
  2. Si la propriété est une structure de champs exposés, les champs de la structure peuvent être modifiés sans avoir à en faire des copies redondantes .
  3. Si la méthode `ActByRef` transmet un ou plusieurs paramètres` ref` de son appelant au délégué fourni, il peut être possible d'utiliser un délégué singleton ou statique, évitant ainsi la nécessité de créer des fermetures ou des délégués au moment de l'exécution
  4. La propriété sait quand elle est "travaillée". Bien qu'il soit toujours nécessaire de faire preuve de prudence lors de l'exécution du code externe tout en maintenant un verrou, si l'on peut faire en sorte que les appelants ne fassent pas trop dans leur rappel, ils pourraient avoir besoin d'un autre verrou, il peut être pratique que la méthode garde l'accès à la propriété avec un lock, de telle sorte que les mises à jour incompatibles avec `CompareExchange` puissent toujours être effectuées de manière quasi atomique .

Passer des choses soit ref est un excellent modèle; dommage que ce ne soit plus utilisé .

3
supercat

Juste une petite extension de la solution Linq Expression de Nathan . Utilisez plusieurs paramètres génériques pour que la propriété ne se limite pas à une chaîne.

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);
        }
    }
}
3
Zick Zhang

Ce n'est pas possible. Tu pourrais dire

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

WorkPhone est une propriété string en écriture et la définition de GetString est remplacée par

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

Cela aura la même sémantique que vous semblez vouloir.

Ce n'est pas possible car une propriété est en réalité une paire de méthodes déguisées. Chaque propriété met à la disposition des getters et des setters accessibles via une syntaxe de type champ. Lorsque vous essayez d'appeler GetString comme vous l'avez proposé, ce que vous transmettez est une valeur et non une variable. La valeur que vous transmettez est celle renvoyée par le getter get_WorkPhone.

2
jason

Ceci est couvert dans la section 7.4.1 de la spécification du langage C #. Seule une référence de variable peut être passée en tant que paramètre ref ou out dans une liste d'arguments. Une propriété ne constitue pas une référence de variable et ne peut donc pas être utilisée. 

2
JaredPar

Si vous voulez obtenir et définir la propriété à la fois, vous pouvez l'utiliser 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);
    }
}
1
Pellet

Ce que vous pouvez essayer de faire est de créer un objet pour contenir la valeur de la propriété. De cette façon, vous pouvez passer l'objet et toujours avoir accès à la propriété à l'intérieur.

1
Anthony Reese

Vous ne pouvez pas ref une propriété, mais si vos fonctions ont besoin des accès get et set, vous pouvez passer autour d'une instance d'une classe avec une propriété définie:

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

Exemple:

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

Les propriétés ne peuvent pas être transmises par référence? Faites-en un champ et utilisez la propriété pour le référencer publiquement:

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
    }
}
0
macedo123