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


224

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

GetString(
    inputString,
    ref Client.WorkPhone)

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

这给了我一个编译错误。我认为我正在努力实现的目标很明确。基本上,我想GetString将输入字符串的内容复制到的WorkPhone属性Client

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


至于为什么,请参阅此stackoverflow.com/questions/564557/…–
nawfal

Answers:


423

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

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

2
喜欢这些例子。我发现这也是扩展方法的好地方:codepublic static string GetValueOrDefault(this string s,string isNullString){if(s == null){s = isNullString; } return s; } void Main(){person.MobilePhone.GetValueOrDefault(person.WorkPhone); }
BlackjacketMack 2012年

9
在解决方案2中,getOutput不需要第二个参数。
贾德

31
我认为解决方案3的更好称呼是Reflection。
贾德

1
在解决方案2中,第二个参数getOutput是不必要的-true,但是我在GetString中使用了它来查看所设置的值。不知道如何在没有此参数的情况下执行此操作。
Petras

3
@GoneCodingGoodbye:但是效率最低的方法。使用反射简单地为属性分配值就像用大锤砸破螺母一样。同样,GetString应该设置属性的方法显然被错误命名。
蒂姆·施梅尔特

27

而不复制属性

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

4
+1将名称更改GetStringNullSafeSet,因为在这里前者没有意义。
卡米洛·马丁

25

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

3
最好的答案在这里。您知道对性能有何影响?最好将其包含在答案中。我对表达式树不太熟悉,但是我希望使用Compile()意味着访问器实例实际上包含IL编译代码,因此可以使用n倍的常数访问器,但是可以使用总共n个访问器(高的成本)不会。
mancze

很棒的代码!我认为,这是最好的答案。最通用的一种。就像mancze所说的那样……它应该对性能产生巨大影响,并且仅应在代码清晰度比性能更重要的情况下使用。
Eric Ouellet,

5

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

3

尚未提及的另一个技巧是让实现属性的类(例如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)(以及可能的两个和三个“额外参数”的版本),该类将传递其内部表示形式Foo为提供的过程作为ref参数。与使用该属性的其他方法相比,这有两个很大的优点:

  1. 该属性被“就地”更新;如果属性的类型与“互锁”方法兼容,或者它是具有此类暴露字段的结构,则可以使用“互锁”方法对属性进行原子更新。
  2. 如果该属性是暴露字段结构,则可以修改该结构的字段,而不必对其进行任何冗余复制。
  3. 如果ActByRef方法将一个或多个ref参数从其调用者传递到提供的委托,则可以使用单例或静态委托,从而避免在运行时创建闭包或委托。
  4. 该物业知道何时与之“合作”。虽然始终需要谨慎地在持有锁的同时执行外部代码,但是如果可以信任调用者在回调中不要做任何可能需要另一把锁的事情,那么使用一种方法来保护属性访问可能是实用的。锁定,这样与CompareExchange不兼容的更新仍可以准原子执行。

传递事物ref是一种极好的模式;太糟糕了,它不再使用了。


3

只是对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);
        }
    }
}

2

C#语言规范的7.4.1节对此进行了介绍。参数列表中只能将变量引用作为ref或out参数传递。属性没有资格作为变量引用,因此无法使用。


2

这是不可能的。你可以说

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

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

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

这将具有您似乎想要的语义。

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


1

您可以尝试创建一个对象来保存属性值。这样,您可以传递对象,但仍然可以访问其中的属性。


1

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

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

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

0

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

var phone = Client.WorkPhone;
GetString(input, ref phone);
Client.WorkPhone = phone;

0

要对此问题进行投票,这里是有关如何将其添加到语言中的一项积极建议。我并不是说这是最好的方法(完全),请随时提出自己的建议。但是允许像Visual Basic这样的ref传递属性已经可以做到,这将极大地简化某些代码,而且非常常见!

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.