在C#中通过引用传递属性
-
05-07-2019 - |
题
我正在尝试执行以下操作:
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表达式
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
将<=>的内部表示作为<=>参数传递给提供的过程。与其他使用该属性的方法相比,这有一些很大的优势:
- 该属性已更新<!>“;就位<!>”;;如果属性是与`Interlocked`方法兼容的类型,或者如果它是具有此类类型的公开字段的结构,则可以使用`Interlocked`方法对属性执行原子更新。
- 如果属性是公开字段结构,则可以修改结构的字段,而不必为其创建任何冗余副本。
- 如果`ActByRef`方法将一个或多个`ref`参数从其调用者传递给提供的委托,则可以使用单例或静态委托,从而避免创建闭包或代表们在运行时。
- 该属性知道何时<!>使用<!>“;虽然在持有锁时始终需要谨慎执行外部代码,但是如果可以信任调用者不要在其回调中执行任何可能需要另外锁定的操作,那么使用该方法保护属性访问权限可能是切实可行的。锁定,这样与“CompareExchange”不兼容的更新仍可以准原子方式执行。
传递事物<=>是一种很好的模式;太糟糕了,它没有被更多地使用。
只需稍微扩展一下 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);
}
}
}
如果您想要获取和设置属性,可以在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);
}
}
这在C#语言规范的第7.4.1节中有所介绍。只有变量引用可以作为参数列表中的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;
}
这将具有您似乎尝试的相同语义。
这是不可能的,因为属性实际上是伪装的一对方法。每个属性都提供可通过字段语法访问的getter和setter。当您尝试按照您的建议调用get_WorkPhone
时,您传入的是值而不是变量。您传入的值是从getter <=>返回的值。
您可以尝试创建一个对象来保存属性值。这样你就可以传递对象并仍然可以访问里面的属性。
您不能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
}
}