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)
(および場合によっては2と3のバージョン<!> quot; extra parameters <!> quot;も同様))ref
の内部表現を、指定されたプロシージャに<=>パラメータとして渡します。これには、プロパティを操作する他の方法に比べていくつかの大きな利点があります。
- プロパティが更新されます<!> quot; inplace <!> quot ;;プロパティが `Interlocked`メソッドと互換性のあるタイプである場合、またはそのようなタイプの公開フィールドを持つ構造体である場合、` Interlocked`メソッドを使用してプロパティのアトミック更新を実行できます。
- プロパティが公開フィールド構造である場合、構造のフィールドは、冗長なコピーを作成せずに変更できます。
- `ActByRef`メソッドが呼び出し元から指定されたデリゲートに1つ以上の` ref`パラメーターを渡す場合、シングルトンまたは静的デリゲートを使用できるため、クロージャーを作成したり、実行時のデリゲート。
- プロパティは、いつ<!> quot; <!> quot;と連携するかを認識します。ロックを保持しながら外部コードを実行する場合は常に注意する必要がありますが、別のロックを必要とする可能性があるコールバックで何もしないように呼び出し元を信頼できる場合は、メソッドでプロパティアクセスを保護することが実用的ですロック。これにより、 `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;
}
これは、あなたが試みているように見えるのと同じセマンティクスを持ちます。
プロパティは実際には変装したメソッドのペアであるため、これは不可能です。各プロパティは、フィールドのような構文を介してアクセス可能なゲッターとセッターを使用可能にします。提案したように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
}
}