C#で参照によりプロパティを渡す


224

私は次のことをやろうとしています:

GetString(
    inputString,
    ref Client.WorkPhone)

private void GetString(string inValue, ref string outValue)
{
    if (!string.IsNullOrEmpty(inValue))
    {
        outValue = inValue;
    }
}

これは私にコンパイルエラーを与えています。私が達成しようとしていることはかなり明確だと思います。基本的にGetString、入力文字列の内容をのWorkPhoneプロパティにコピーしますClient

プロパティを参照で渡すことは可能ですか?


理由については、このstackoverflow.com/questions/564557/…を
nawfal

回答:


423

プロパティを参照で渡すことはできません。この制限を回避する方法はいくつかあります。

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))
    {
        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");
}

2
例が大好きです。これも拡張メソッドに最適な場所だとcode思います。public static string GetValueOrDefault(this string s、string isNullString){if(s == null){s = isNullString; } 戻り値; } void Main(){person.MobilePhone.GetValueOrDefault(person.WorkPhone); }
BlackjacketMack

9
ソリューション2では、2番目のパラメーターgetOutputは不要です。
Jaider

31
そして、ソリューション3のより良い名前はリフレクションです。
Jaider

1
ソリューション2では、2番目のパラメーターgetOutputは不要です-trueですが、それをGetString内で使用して、設定している値を確認しました。このパラメーターなしでそれを行う方法がわかりません。
Petras 2013年

3
@GoneCodingGoodbye:しかし、最も効率の悪いアプローチ。リフレクションを使用してプロパティに値を単に割り当てることは、大槌を使ってナッツを割るようなものです。また、GetStringプロパティを設定することになっているメソッドは明らかに誤った名前が付けられています。
Tim Schmelter 2016

27

プロパティを複製せずに

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);
    }
}

4
前者はここでは意味がないため、名前GetStringNullSafeSetに変更すると+1 。
Camilo Martin

25

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");

3
ここでベストアンサー。パフォーマンスへの影響を知っていますか?回答の範囲内に収めておくとよいでしょう。式ツリーにはあまり詳しくありませんが、Compile()を使用すると、アクセサーインスタンスに実際にILでコンパイルされたコードが含まれるため、定数のアクセサーをn回使用しても問題ありませんが、合計n個のアクセサーを使用すると思います(高額の俳優のコスト)はそうではありません。
Mancze '15

素晴らしいコード!私の意見では、それが最良の答えです。最も一般的なもの。manczeのように...これはパフォーマンスに大きな影響を与えるはずであり、パフォーマンスよりもコードの明確性が重要なコンテキストでのみ使用する必要があります。
エリックウエレット

5

両方のプロパティを取得および設定する場合は、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);
    }
}

3

まだ言及されていないもう1つのトリックは、プロパティ(たとえばFooBar)を実装するクラスにデリゲート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つの「追加パラメーター」のバージョンも)を実装して、の内部表現を渡すFooことです。refパラメータとして提供されたプロシージャ。これには、プロパティを操作する他の方法よりも大きな利点がいくつかあります。

  1. プロパティは「インプレース」で更新されます。プロパティが `Interlocked`メソッドと互換性のあるタイプである場合、またはそのようなタイプの公開されたフィールドを持つ構造体である場合、` Interlocked`メソッドを使用してプロパティのアトミック更新を実行できます。
  2. プロパティが公開フィールド構造の場合、構造のフィールドは、それの冗長なコピーを作成する必要なしに変更できます。
  3. `ActByRef`メソッドが1つ以上の` ref`パラメーターをその呼び出し元から提供されたデリゲートに渡す場合、シングルトンまたは静的デリゲートを使用できるため、実行時にクロージャーまたはデリゲートを作成する必要がなくなります。
  4. プロパティは、いつ「処理」されるかを認識しています。ロックを保持しながら外部コードを実行する場合は常に注意を払う必要がありますが、コールバックで別のロックが必要になる可能性のある処理を実行しないように呼び出し元を信頼できる場合は、ロック。`CompareExchange`と互換性のない更新は、準原子的に実行できます。

物事を渡すことrefは優れたパターンです。あまりにも悪く、それ以上使用されていません。


3

ネイサンのLinq式ソリューションのほんの少しの拡張。プロパティが文字列に限定されないように、複数の汎用パラメーターを使用します。

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);
        }
    }
}

2

これは、C#言語仕様のセクション7.4.1で説明されています。変数参照のみを、引数リストのrefまたはoutパラメーターとして渡すことができます。プロパティは変数参照としての資格がないため、使用できません。


2

これは不可能です。あなたは言えた

Client.WorkPhone = GetString(inputString, Client.WorkPhone);

どこWorkPhone書き込み可能であるstring財産との定義GetStringに変更されては、

private string GetString(string input, string current) { 
    if (!string.IsNullOrEmpty(input)) {
        return input;
    }
    return current;
}

これは、あなたが試みているように見えるのと同じセマンティクスを持ちます。

プロパティは実際には変装したメソッドのペアであるため、これは不可能です。各プロパティは、フィールドのような構文を介してアクセス可能なゲッターとセッターを利用できるようにします。GetStringあなたが提案したようにあなたが呼び出すことを試みるとき、あなたが渡しているのは値ではなく変数です。渡す値は、ゲッターから返された値ですget_WorkPhone


1

あなたがしようとすることができることは、プロパティ値を保持するオブジェクトを作成することです。これにより、オブジェクトを渡しても、内部のプロパティにアクセスできます。


1

プロパティを参照で渡すことはできませんか?次に、それをフィールドにして、プロパティを使用してパブリックに参照します。

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
    }
}

0

refプロパティは使用できませんが、関数で両方getsetアクセスする必要がある場合は、プロパティが定義されたクラスのインスタンスを渡すことができます。

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);
    }
}

0

受け入れられた答えは、その関数がコード内にあり、それを変更できる場合に適しています。ただし、外部ライブラリのオブジェクトと関数を使用する必要があり、プロパティと関数の定義を変更できない場合があります。その後、一時変数を使用できます。

var phone = Client.WorkPhone;
GetString(input, ref phone);
Client.WorkPhone = phone;

0

この問題に投票するために、これを言語に追加する方法についての積極的な提案を1つ示します。これが(まったく)これを行うための最良の方法であるとは言っていません。自由に自分の提案を出してください。しかし、Visual Basicが既に実行できるように、参照によってプロパティを渡すことができるようにすると、一部のコードを単純化するのに非常に役立ちます。

https://github.com/dotnet/csharplang/issues/1235

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.