275

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

GetString(
    inputString,
    ref Client.WorkPhone)

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

这给了我一个编译错误。我认为它很清楚我想要实现的目标。基本上我想GetString将输入字符串的内容复制WorkPhoneClient.

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

4

14 回答 14

496

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

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 表达式

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))
    {
        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");
}
于 2009-09-10T01:55:33.417 回答
34

不复制属性

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);
    }
}
于 2012-02-01T17:13:17.660 回答
32

我使用 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");
于 2017-04-19T14:40:58.377 回答
8

如果你想同时获取和设置属性,你可以在 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);
    }
}
于 2018-02-22T03:16:05.140 回答
4

只是对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);
        }
    }
}
于 2015-09-01T16:18:41.737 回答
3

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

于 2009-09-10T00:27:23.227 回答
3

另一个尚未提及的技巧是让实现属性(例如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. 该属性已“就地”更新;如果属性的类型与“Interlocked”方法兼容,或者如果它是具有此类类型的公开字段的结构,则可以使用“Interlocked”方法对属性执行原子更新。
  2. 如果属性是公开字段结构,则可以修改结构的字段而无需对其进行任何冗余副本。
  3. 如果 `ActByRef` 方法将一个或多个 `ref` 参数从其调用者传递给提供的委托,则可以使用单例或静态委托,从而避免在运行时创建闭包或委托。
  4. 该物业知道何时“与之合作”。虽然在持有锁的同时执行外部代码时总是需要谨慎,但如果可以相信调用者不会在其回调中做任何可能需要另一个锁的事情,那么让该方法使用锁,这样与 `CompareExchange` 不兼容的更新仍然可以准原子地执行。

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

于 2012-12-16T21:37:13.637 回答
2

这是不可能的。你可以说

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

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

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

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

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

于 2009-09-10T00:23:02.010 回答
2

属性不能通过引用传递?然后将其设为字段,并使用该属性公开引用它:

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
    }
}
于 2019-06-29T05:42:24.090 回答
1

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

于 2010-06-02T15:58:14.200 回答
0

您不能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);
    }
}
于 2017-10-08T20:11:54.073 回答
0

如果该函数在您的代码中并且您可以修改它,那么接受的答案就很好。但有时您必须使用某个外部库中的对象和函数,并且您无法更改属性和函数定义。然后你可以只使用一个临时变量。

var phone = Client.WorkPhone;
GetString(input, ref phone);
Client.WorkPhone = phone;
于 2019-10-09T08:54:14.053 回答
0

为了对这个问题进行投票,这里有一个关于如何将其添加到语言中的积极建议。我并不是说这是最好的方法(完全),请随时提出您自己的建议。但是允许像 Visual Basic 那样通过 ref 传递属性将极大地帮助简化一些代码,而且经常如此!

https://github.com/dotnet/csharplang/issues/1235

于 2020-03-10T17:28:29.640 回答
0

您似乎需要对该字段施加业务规则约束,同时希望尽可能保持代码 DRY。

它是可以实现的,并且还可以通过在该字段上实现完整的属性并使用您的可重用方法来保留您的域语义:

public class Client
{
    private string workPhone;

    public string WorkPhone
    {
        get => workPhone;
        set => SafeSetString(ref workPhone, value);
    }

    private void SafeSetString(ref string target, string source)
    {
        if (!string.IsNullOrEmpty(source))
        {
            target = source;
        }
    }
}

SafeSetString 方法可以放置在 Utilities 类中或任何有意义的地方。

于 2021-12-21T07:42:26.943 回答