Вопрос

Я пытаюсь сделать следующее:

GetString(
    inputString,
    ref Client.WorkPhone)

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

Это дает мне ошибку компиляции.Я думаю, довольно ясно, чего я пытаюсь достичь.В принципе я хочу GetString скопировать содержимое входной строки в WorkPhone свойство Client.

Можно ли передать свойство по ссылке?

Это было полезно?

Решение

Свойства не могут быть переданы по ссылке. Вот несколько способов обойти это ограничение.

1. Возвращаемое значение

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. Делегат

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. LINQ Expression

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. Отражение

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

Другие советы

без дублирования свойства

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

Я написал оболочку, используя вариант ExpressionTree и c # 7 (если кому-то интересно):

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

И используйте это как:

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

Еще одна хитрость, которая еще не упоминалась, - это иметь класс, который реализует свойство (например, Foo типа Bar), также определять делегат delegate void ActByRef<T1,T2>(ref T1 p1, ref T2 p2); и реализовывать метод ActOnFoo<TX1>(ref Bar it, ActByRef<Bar,TX1> proc, ref TX1 extraParam1) (и, возможно, версии для двух и трех). " дополнительные параметры " также), который передаст свое внутреннее представление ref предоставленной процедуре в качестве параметра <=>. Это имеет пару больших преимуществ по сравнению с другими методами работы со свойством:

  1. Свойство обновлено " на месте " ;; если свойство имеет тип, который совместим с методами Interlocked, или если это структура с открытыми полями таких типов, методы Interlocked могут использоваться для выполнения атомарных обновлений свойства.
  2. Если свойство является структурой открытого поля, поля структуры могут быть изменены без необходимости создания избыточных копий.
  3. Если метод ActByRef передает один или несколько параметров ref от своего вызывающего к предоставленному делегату, может быть возможно использовать одноэлементный или статический делегат, таким образом избегая необходимости создавать замыкания или делегаты во время выполнения.
  4. Свойство знает, когда оно " работает с " ;. Хотя всегда необходимо соблюдать осторожность при выполнении внешнего кода при удержании блокировки, если можно доверять вызывающим сторонам, чтобы они не делали в своем обратном вызове ничего, что могло бы потребовать другой блокировки, может оказаться целесообразным, чтобы метод защищал доступ к свойству с помощью блокировка, чтобы обновления, не совместимые с `CompareExchange`, могли выполняться квазиатомно.

    Передача вещей быть <=> - отличный пример; Жаль, что он больше не используется.

Небольшое дополнение к решению Натана Linq Expression . Используйте универсальный параметр multi, чтобы свойство не ограничивалось строкой.

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

Если вы хотите получить и установить оба свойства, вы можете использовать это в 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);
    }
}

Это описано в разделе 7.4.1 спецификации языка C #. В качестве параметра ref или out в списке аргументов может быть передана только ссылка на переменную. Свойство не квалифицируется как ссылка на переменную и, следовательно, не может быть использовано.

Это невозможно. Вы могли бы сказать

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

где WorkPhone является записываемым string свойством, а определение GetString изменено на

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

Это будет иметь ту же семантику, которую вы, похоже, пытаетесь найти.

Это невозможно, потому что свойство - это действительно замаскированная пара методов. Каждое свойство делает доступными методы получения и установки, доступные через полевой синтаксис. Когда вы пытаетесь вызвать get_WorkPhone, как вы предложили, вы передаете значение, а не переменную. Значение, которое вы передаете, возвращено из получателя <=>.

То, что вы можете попытаться сделать, это создать объект для хранения значения свойства. Таким образом, вы можете передать объект и по-прежнему иметь доступ к свойству внутри.

Ты не можешь ref свойство, но если вашим функциям нужны оба get и set доступа вы можете передать экземпляр класса с определенным свойством:

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

Пример:

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

Свойства не могут быть переданы по ссылке? Затем сделайте это поле и используйте свойство для публичной ссылки на него:

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
    }
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top