[英]Passing properties by reference in C#
我正在嘗試執行以下操作:
GetString(
inputString,
ref Client.WorkPhone)
private void GetString(string inValue, ref string outValue)
{
if (!string.IsNullOrEmpty(inValue))
{
outValue = inValue;
}
}
這給了我一個編譯錯誤。 我認為它很清楚我想要實現的目標。 基本上我希望GetString
將輸入字符串的內容復制到Client
的WorkPhone
屬性。
是否可以通過引用傳遞屬性?
屬性不能通過引用傳遞。 您可以通過以下幾種方法來解決此限制。
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");
}
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");
}
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");
}
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");
}
不復制屬性
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");
如果你想同時獲取和設置屬性,你可以在 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);
}
}
只是對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);
}
}
}
另一個尚未提及的技巧是讓實現屬性的類(例如Bar
類型的Foo
)也定義一個委托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
參數。 與使用該屬性的其他方法相比,這有幾個很大的優勢:
將事物傳遞給ref
是一種極好的模式; 太糟糕了,它沒有被更多地使用。
這在 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。 當您嘗試按照建議調用GetString
,您傳入的是值而不是變量。 您傳入的值是從 getter get_WorkPhone
返回的get_WorkPhone
。
屬性不能通過引用傳遞? 然后將其設為字段,並使用該屬性公開引用它:
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
}
}
您可以嘗試做的是創建一個對象來保存屬性值。 這樣你就可以傳遞對象並且仍然可以訪問里面的屬性。
你不能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);
}
}
如果該功能在您的代碼中並且您可以修改它,那么接受的答案是好的。 但有時您必須使用某個外部庫中的對象和函數,並且無法更改屬性和函數定義。 然后你可以只使用一個臨時變量。
var phone = Client.WorkPhone;
GetString(input, ref phone);
Client.WorkPhone = phone;
為了對這個問題進行投票,這里有一個關於如何將其添加到語言中的積極建議。 我並不是說這是最好的方法(完全),請隨意提出您自己的建議。 但是允許屬性通過 ref 傳遞,就像 Visual Basic 已經可以做到的那樣,將極大地幫助簡化一些代碼,而且經常如此!
您似乎需要對該字段施加業務規則約束,同時希望盡可能保持代碼干燥。
通過在該字段上實現完整屬性並使用可重用的方法,它是可以實現的,並且還可以保留您的域語義:
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 類中或任何有意義的地方。
是的,您不能傳遞屬性,但您可以將屬性轉換為具有支持字段的屬性並執行類似的操作。
public class SomeClass
{
private List<int> _myList;
public List<int> MyList
{
get => return _myList;
set => _myList = value;
}
public ref List<int> GetMyListByRef()
{
return ref _myList;
}
}
但是有更好的解決方案,例如動作委托等。
受Sven 的表達式樹解決方案的啟發,下面是一個不依賴反射的簡化版本。 此外,它還刪除了不必要的自定義 getter 和字段表達式。
using System;
using System.Linq.Expressions;
namespace Utils;
public class Accessor<T>
{
public Accessor(Expression<Func<T>> expression)
{
if (expression.Body is not MemberExpression memberExpression)
throw new ArgumentException("expression must be return a field or property");
var parameterExpression = Expression.Parameter(typeof(T));
_setter = Expression.Lambda<Action<T>>(Expression.Assign(memberExpression, parameterExpression), parameterExpression).Compile();
_getter = expression.Compile();
}
public void Set(T value) => _setter(value);
public T Get() => _getter();
private readonly Action<T> _setter;
private readonly Func<T> _getter;
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.