Передача свойств по ссылке в 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 Expression
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`, могли выполняться квазиатомно.
Литий> ол>
Передача вещей быть <=> - отличный пример; Жаль, что он больше не используется. Р>
Небольшое дополнение к решению Натана Linq Expression . Используйте универсальный параметр multi, чтобы свойство не ограничивалось строкой.
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);
}
}
Это описано в разделе 7.4.1 спецификации языка C #. В качестве параметра 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;
}
Это будет иметь ту же семантику, которую вы, похоже, пытаетесь найти.
Это невозможно, потому что свойство - это действительно замаскированная пара методов. Каждое свойство делает доступными методы получения и установки, доступные через полевой синтаксис. Когда вы пытаетесь вызвать get_WorkPhone
, как вы предложили, вы передаете значение, а не переменную. Значение, которое вы передаете, возвращено из получателя <=>.
То, что вы можете попытаться сделать, это создать объект для хранения значения свойства. Таким образом, вы можете передать объект и по-прежнему иметь доступ к свойству внутри.
Ты не можешь 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
}
}