web-dev-qa-db-ja.com

C#での参照によるプロパティの受け渡し

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

GetString(
    inputString,
    ref Client.WorkPhone)

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

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

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

200
yogibear

プロパティは参照渡しできません。この制限を回避する方法は次のとおりです。

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");
}
375
Nathan Baulch

プロパティを複製することなく

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

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

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

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

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

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

3
supercat

Nathan's Linq Expression solution へのちょっとした拡張。プロパティが文字列に限定されないように、複数の汎用パラメーターを使用します。

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);
        }
    }
}
3
Zick Zhang

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

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から返された値です。

2
jason

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

2
JaredPar

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

1
Anthony Reese

プロパティを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
chess123mate

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

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
macedo123