Przekazywanie właściwości przez odniesienie w C#

Staram się wykonać następujące czynności:

GetString(
    inputString,
    ref Client.WorkPhone)

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

To daje mi błąd kompilacji. Myślę, że to jasne, co próbuję osiągnąć. Zasadniczo chcę GetString skopiować zawartość łańcucha wejściowego do Właściwości WorkPhone Client.

Czy można przekazać nieruchomość przez odniesienie?

Author: AFract, 2009-09-10

10 answers

Właściwości nie mogą być przekazywane przez odniesienie. Oto kilka sposobów obejścia tego ograniczenia.

1. Return Value

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. Delegat

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. Wyrażenie 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. Reflection

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");
}
 333
Author: Nathan Baulch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-28 16:04:13

Bez powielania właściwości

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);
    }
}
 22
Author: Firo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-24 09:47:30

Napisałem wrapper używając wariantu ExpressionTree i c#7 (jeśli ktoś jest zainteresowany):

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

I używaj go jak:

var accessor = new Accessor<string>(() => myClient.WorkPhone);
accessor.Set("12345");
Assert.Equal(accessor.Get(), "12345");
 14
Author: Sven,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-19 14:40:58

Inną sztuczką, o której jeszcze nie wspomniano, jest posiadanie klasy, która implementuje właściwość (np. Foo typu Bar) również definiuje delegata delegate void ActByRef<T1,T2>(ref T1 p1, ref T2 p2); i implementuje metodę ActOnFoo<TX1>(ref Bar it, ActByRef<Bar,TX1> proc, ref TX1 extraParam1) (i ewentualnie wersje dla dwóch i trzech "dodatkowych parametrów"), która przekaże swoją wewnętrzną reprezentację Foo do dostarczonej procedury jako parametr ref. Ma to kilka dużych zalet w stosunku do innych metod pracy z właściwością:

  1. nieruchomość jest aktualizowana "na miejscu"; jeśli nieruchomość jest typu zgodnego z metodami "Interlocked" lub jeśli jest strukturą z odsłoniętymi polami takich typów, metody "Interlocked" mogą być użyte do wykonywania atomowych aktualizacji właściwości.
  2. jeśli właściwość jest strukturą exposed-field, pola tej struktury mogą być modyfikowane bez konieczności tworzenia jej zbędnych kopii.
  3. jeśli metoda 'ActByRef' przekazuje jeden lub więcej parametrów `ref` od wywołującego do dostarczonego delegata, może być możliwe użyj pojedynczego lub statycznego delegata, unikając w ten sposób konieczności tworzenia zamknięć lub delegatów w czasie wykonywania.
  4. nieruchomość wie, kiedy jest "obrabiana". Chociaż zawsze konieczne jest zachowanie ostrożności podczas wykonywania zewnętrznego kodu podczas trzymania blokady, jeśli można zaufać wywołującym, że nie zrobią zbyt wiele w ich wywołaniu zwrotnym, które może wymagać innej blokady, może być praktyczne, aby metoda chroniła dostęp do właściwości za pomocą blokady, tak aby aktualizacje, które nie są zgodne z `CompareExchange " może być nadal wykonywane quasi-atomicznie.

Przechodzenie rzeczy be ref to doskonały wzór; szkoda, że nie jest używany więcej.

 3
Author: supercat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-16 21:37:13

Jest to opisane w sekcji 7.4.1 specyfikacji języka C#. Tylko zmienna-reference może być przekazywana jako parametr ref lub out na liście argumentów. Właściwość nie kwalifikuje się jako odniesienie do zmiennej i dlatego nie może być używana.

 2
Author: JaredPar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-10 00:27:23

To niemożliwe. Można powiedzieć

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

Gdzie WorkPhone jest zapisywalną właściwością string, a definicja {[4] } zmienia się na

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

Będzie to miało tę samą semantykę, o którą się starasz.

Nie jest to możliwe, ponieważ właściwość jest naprawdę parą metod w ukryciu. Każda właściwość udostępnia gettery i settery, które są dostępne za pomocą składni podobnej do pól. Kiedy próbujesz zadzwonić GetString tak, jak zaproponowałeś, to, co przekazujesz, jest wartością i nie zmienna. Wartość, którą przekazujesz, jest zwracana z gettera get_WorkPhone.

 2
Author: jason,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-18 08:49:46

Małe rozszerzenie doRozwiązania LINQ . Użyj multi generic param, aby właściwość nie ograniczała się do ciągu znaków.

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);
        }
    }
}
 2
Author: Zick Zhang,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-18 08:50:15

Możesz spróbować utworzyć obiekt do przechowywania wartości właściwości. W ten sposób możesz przejść przez obiekt i nadal mieć dostęp do nieruchomości w środku.

 1
Author: Anthony Reese,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-02 15:58:14

Jeśli chcesz uzyskać i ustawić obie właściwości, możesz użyć tego w 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
Author: Pellet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-22 03:16:05

Nie możesz ref właściwości, ale jeśli twoje funkcje potrzebują zarówno dostępu get, jak i set, możesz przekazać instancję klasy o zdefiniowanej właściwości:

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

Przykład:

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
Author: chess123mate,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-08 20:11:54