Passing properties by reference in C#
我正在尝试执行以下操作:
1 2 3 4 5 6 7 8 9 10 11 | GetString( inputString, ref Client.WorkPhone) private void GetString(string inValue, ref string outValue) { if (!string.IsNullOrEmpty(inValue)) { outValue = inValue; } } |
这给了我一个编译错误。我想我想要达到的目标很清楚。基本上,我希望
是否可以通过引用传递属性?
属性不能通过引用传递。下面是一些解决这个限制的方法。
1。返回值1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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。代表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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"); } |
三。LINQ表达式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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。反射
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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"); } |
不复制属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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(如果有人感兴趣)编写了一个包装器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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(); } |
使用方法如下:
1 2 3 | var accessor = new Accessor<string>(() => myClient.WorkPhone); accessor.Set("12345"); Assert.Equal(accessor.Get(),"12345"); |
如果要同时获取和设置属性,可以在C 7中使用:
1 2 3 4 5 6 7 8 9 10 11 | 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); } } |
只是对Nathan的Linq表达式解决方案进行了一点扩展。使用多个通用参数,以便属性不限于字符串。
1 2 3 4 5 6 7 8 9 10 11 12 | 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); } } } |
另一个尚未提到的技巧是让实现某个属性的类(例如,
以东传1〔10〕是一个很好的模式,可惜没有更多的使用。
这在C语言规范的第7.4.1节中有介绍。只有变量引用可以作为参数列表中的引用或out参数传递。属性不限定为变量引用,因此无法使用。
这是不可能的。你可以说
1 | Client.WorkPhone = GetString(inputString, Client.WorkPhone); |
其中
1 2 3 4 5 6 | private string GetString(string input, string current) { if (!string.IsNullOrEmpty(input)) { return input; } return current; } |
这将具有与您试图寻找的相同的语义。
这是不可能的,因为属性实际上是一对伪装的方法。每个属性都提供可用的getter和setter,这些getter和setter可以通过类似字段的语法进行访问。当您试图按照您的建议调用
您可以尝试创建一个对象来保存属性值。这样,您就可以传递对象,并且仍然可以访问其中的属性。
您不能访问
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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; } } |
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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); } } |