J'essaie de faire ce qui suit:
GetString(
inputString,
ref Client.WorkPhone)
private void GetString(string inValue, ref string outValue)
{
if (!string.IsNullOrEmpty(inValue))
{
outValue = inValue;
}
}
Cela me donne une erreur de compilation. Je pense que ce que j'essaie de faire est très clair. En gros, je veux que GetString
copie le contenu d'une chaîne d'entrée dans la propriété WorkPhone
de Client
.
Est-il possible de transmettre une propriété par référence?
Les propriétés ne peuvent pas être transmises par référence. Voici quelques façons de contourner cette limitation.
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))
{
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");
}
sans dupliquer la propriété
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);
}
}
J'ai écrit un wrapper utilisant la variante ExpressionTree et le c # 7 (si quelqu'un est intéressé):
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();
}
Et utilisez-le comme:
var accessor = new Accessor<string>(() => myClient.WorkPhone);
accessor.Set("12345");
Assert.Equal(accessor.Get(), "12345");
Une autre astuce non encore mentionnée consiste à ce que la classe qui implémente une propriété (par exemple Foo
de type Bar
) définisse également un délégué delegate void ActByRef<T1,T2>(ref T1 p1, ref T2 p2);
et implémente une méthode ActOnFoo<TX1>(ref Bar it, ActByRef<Bar,TX1> proc, ref TX1 extraParam1)
(et éventuellement des versions pour deux et trois "paramètres supplémentaires") représentation interne de Foo
à la procédure fournie sous la forme d'un paramètre ref
. Cela présente quelques gros avantages par rapport aux autres méthodes de travail avec la propriété:
Passer des choses soit ref
est un excellent modèle; dommage que ce ne soit plus utilisé .
Juste une petite extension de la solution Linq Expression de Nathan . Utilisez plusieurs paramètres génériques pour que la propriété ne se limite pas à une chaîne.
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);
}
}
}
Ce n'est pas possible. Tu pourrais dire
Client.WorkPhone = GetString(inputString, Client.WorkPhone);
où WorkPhone
est une propriété string
en écriture et la définition de GetString
est remplacée par
private string GetString(string input, string current) {
if (!string.IsNullOrEmpty(input)) {
return input;
}
return current;
}
Cela aura la même sémantique que vous semblez vouloir.
Ce n'est pas possible car une propriété est en réalité une paire de méthodes déguisées. Chaque propriété met à la disposition des getters et des setters accessibles via une syntaxe de type champ. Lorsque vous essayez d'appeler GetString
comme vous l'avez proposé, ce que vous transmettez est une valeur et non une variable. La valeur que vous transmettez est celle renvoyée par le getter get_WorkPhone
.
Ceci est couvert dans la section 7.4.1 de la spécification du langage C #. Seule une référence de variable peut être passée en tant que paramètre ref ou out dans une liste d'arguments. Une propriété ne constitue pas une référence de variable et ne peut donc pas être utilisée.
Si vous voulez obtenir et définir la propriété à la fois, vous pouvez l'utiliser en 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);
}
}
Ce que vous pouvez essayer de faire est de créer un objet pour contenir la valeur de la propriété. De cette façon, vous pouvez passer l'objet et toujours avoir accès à la propriété à l'intérieur.
Vous ne pouvez pas ref
une propriété, mais si vos fonctions ont besoin des accès get
et set
, vous pouvez passer autour d'une instance d'une classe avec une propriété définie:
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;
}
}
Exemple:
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);
}
}
Les propriétés ne peuvent pas être transmises par référence? Faites-en un champ et utilisez la propriété pour le référencer publiquement:
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
}
}