Question

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 GetString copier le contenu d'une chaîne d'entrée dans la WorkPhone propriété de Client.

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

Était-ce utile?

La solution

Les propriétés ne peuvent pas être transmises par référence. Voici quelques moyens 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éguer

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

Autres conseils

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

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

Une autre astuce non encore mentionnée est 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 à deux et à trois). " paramètres supplémentaires " également) qui transmettra sa représentation interne de ref à la procédure fournie en tant que paramètre <=>. 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 & "à la 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 à 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 création de fermetures ou délégués au moment de l'exécution.
  4. La propriété sait quand elle est & utilisée avec & ";. 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 dans le rappel une tâche qui pourrait nécessiter un autre verrou, il peut être pratique que la méthode garde l'accès à la propriété avec un verrouillé, de sorte que les mises à jour incompatibles avec `CompareExchange` puissent toujours être effectuées de manière quasi-atomique.

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

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

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

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.

Cela n’est pas possible. Vous pourriez dire

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

WorkPhone est une propriété string accessible en écriture et où 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 celle que vous semblez essayer.

Cela 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 get_WorkPhone 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 <=>.

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 avoir toujours accès à la propriété à l'intérieur.

Vous ne pouvez pas ref une propriété, mais si vos fonctions nécessitent un accès à la fois get et set, vous pouvez faire passer 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);
    }
}

Les propriétés ne peuvent pas être passées 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
    }
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top