在 C# 中通过引用传递属性

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1402803/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 16:07:24  来源:igfitidea点击:

Passing properties by reference in C#

c#propertiespass-by-reference

提问by yogibear

I'm trying to do do the following:

我正在尝试执行以下操作:

GetString(
    inputString,
    ref Client.WorkPhone)

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

This is giving me a compile error. I think its pretty clear what I'm trying to achieve. Basically I want GetStringto copy the contents of an input string to the WorkPhoneproperty of Client.

这给了我一个编译错误。我认为我想要达到的目标非常清楚。基本上,我想GetString以一个输入字符串的内容复制到WorkPhone的财产Client

Is it possible to pass a property by reference?

是否可以通过引用传递属性?

采纳答案by Nathan Baulch

Properties cannot be passed by reference. Here are a few ways you can work around this limitation.

属性不能通过引用传递。您可以通过以下几种方法来解决此限制。

1. Return Value

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

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

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

4. 反思

void GetString(string input, object target, string propertyName)
{
    if (!string.IsNullOrEmpty(input))
    {
        var 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");
}

回答by jason

This is not possible. You could say

这不可能。你可以说

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

where WorkPhoneis a writeable stringproperty and the definition of GetStringis changed to

其中WorkPhone是一个可写string属性,并且 的定义GetString更改为

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

This will have the same semantics that you seem to be trying for.

这将具有您似乎正在尝试的相同语义。

This isn't possible because a property is really a pair of methods in disguise. Each property makes available getters and setters that are accessible via field-like syntax. When you attempt to call GetStringas you've proposed, what you're passing in is a value and not a variable. The value that you are passing in is that returned from the getter get_WorkPhone.

这是不可能的,因为一个属性实际上是一对伪装的方法。每个属性都提供了可通过类似字段的语法访问的 getter 和 setter。当您尝试GetString按照您的建议进行调用时,您传入的是一个值而不是一个变量。您传入的值是从 getter 返回的值get_WorkPhone

回答by JaredPar

This is covered in section 7.4.1 of the C# language spec. Only a variable-reference can be passed as a ref or out parameter in an argument list. A property does not qualify as a variable reference and hence cannot be used.

这在 C# 语言规范的第 7.4.1 节中有介绍。只有变量引用可以作为参数列表中的 ref 或 out 参数传递。属性不符合变量引用的条件,因此不能使用。

回答by Anthony Reese

What you could try to do is create an object to hold the property value. That way you could pass the object and still have access to the property inside.

您可以尝试做的是创建一个对象来保存属性值。这样你就可以传递对象并且仍然可以访问里面的属性。

回答by Firo

without duplicating the property

不复制属性

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

回答by supercat

Another trick not yet mentioned is to have the class which implements a property (e.g. Fooof type Bar) also define a delegate delegate void ActByRef<T1,T2>(ref T1 p1, ref T2 p2);and implement a method ActOnFoo<TX1>(ref Bar it, ActByRef<Bar,TX1> proc, ref TX1 extraParam1)(and possibly versions for two and three "extra parameters" as well) which will pass its internal representation of Footo the supplied procedure as a refparameter. This has a couple of big advantages over other methods of working with the property:

另一个尚未提及的技巧是让实现属性(例如Footype Bar)的类也定义一个委托delegate void ActByRef<T1,T2>(ref T1 p1, ref T2 p2);并实现一个方法ActOnFoo<TX1>(ref Bar it, ActByRef<Bar,TX1> proc, ref TX1 extraParam1)(可能还有两个和三个“额外参数”的版本),该方法将传递其内部表示Footo提供的过程作为ref参数。与使用该属性的其他方法相比,这有几个很大的优势:

  1. The property is updated "in place"; if the property is of a type that's compatible with `Interlocked` methods, or if it is a struct with exposed fields of such types, the `Interlocked` methods may be used to perform atomic updates to the property.
  2. If the property is an exposed-field structure, the fields of the structure may be modified without having to make any redundant copies of it.
  3. If the `ActByRef` method passes one or more `ref` parameters through from its caller to the supplied delegate, it may be possible to use a singleton or static delegate, thus avoiding the need to create closures or delegates at run-time.
  4. The property knows when it is being "worked with". While it is always necessary to use caution executing external code while holding a lock, if one can trust callers not to do too do anything in their callback that might require another lock, it may be practical to have the method guard the property access with a lock, such that updates which aren't compatible with `CompareExchange` could still be performed quasi-atomically.
  1. 该属性已“就地”更新;如果属性的类型与 `Interlocked` 方法兼容,或者它是具有此类类型公开字段的结构,则可以使用 `Interlocked` 方法对属性执行原子更新。
  2. 如果该属性是一个公开字段结构,则可以修改该结构的字段,而无需对其进行任何冗余副本。
  3. 如果`ActByRef` 方法将一个或多个`ref` 参数从其调用者传递给提供的委托,则可能使用单例或静态委托,从而避免在运行时创建闭包或委托的需要。
  4. 该属性知道它何时被“使用”。虽然在持有锁时执行外部代码总是需要谨慎,但如果可以相信调用者不会在他们的回调中做任何可能需要另一个锁的事情,那么让该方法使用锁,这样与 `CompareExchange` 不兼容的更新仍然可以准原子地执行。

Passing things be refis an excellent pattern; too bad it's not used more.

传递事物ref是一种极好的模式;太糟糕了,它没有被更多地使用。

回答by Zick Zhang

Just a little expansion to Nathan's Linq Expression solution. Use multi generic param so that the property doesn't limited to string.

只是对Nathan 的 Linq Expression 解决方案的一点扩展。使用多通用参数,以便属性不限于字符串。

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

回答by Sven

I wrote a wrapper using the ExpressionTree variant and c#7 (if somebody is interested):

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

And use it like:

并像这样使用它:

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

回答by chess123mate

You can't refa property, but if your functions need both getand setaccess you can pass around an instance of a class with a property defined:

您不能ref拥有属性,但如果您的函数需要同时访问getset访问,您可以传递定义了属性的类的实例:

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

Example:

例子:

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

回答by Pellet

If you want to get and set the property both, you can use this in C#7:

如果你想同时获取和设置属性,你可以在 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);
    }
}