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
예제를 좋아하십시오. 나는 이것이 확장 방법에도 훌륭한 장소라는 것을 알았다. codepublic static string GetValueOrDefault (this string s, string isNullString) {if (s == null) {s = isNullString; } return s; } void Main () {person.MobilePhone.GetValueOrDefault (person.WorkPhone); }
BlackjacketMack

9
솔루션 2에서는 두 번째 매개 변수 getOutput가 필요하지 않습니다.
Jaider

31
그리고 솔루션 3의 더 나은 이름은 Reflection이라고 생각합니다.
Jaider

1
솔루션 2에서는 두 번째 매개 변수 getOutput이 필요하지 않습니다. true이지만 GetString 내부에서이 값을 사용하여 설정 한 값을 확인했습니다. 이 매개 변수가 없으면 어떻게 해야할지 모르겠습니다.
Petras

3
@GoneCodingGoodbye : 그러나 가장 효율적인 접근 방식. 리플렉션을 사용하여 단순히 속성에 값을 할당하는 것은 슬레지 해머가 너트를 깨뜨리는 것과 같습니다. 또한 GetString속성을 설정 해야하는 메서드의 이름이 명확하지 않습니다.
Tim Schmelter

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
전자는 여기에서 의미가 없기 때문에 이름 GetString을 로 변경하면 +1입니다 NullSafeSet.
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 명의 접근자를 사용한다는 것을 기대합니다 ( 높은 ctor 비용).
mancze

좋은 코드! 내 의견으로는, 이것이 최선의 대답입니다. 가장 일반적인 것. mancze와 같이 ... 성능에 큰 영향을 미치며 성능보다 코드 선명도가 더 중요한 상황에서만 사용해야합니다.
Eric Ouellet

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

아직 언급되지 않은 또 다른 트릭은 속성 (예 : Footype 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` 메소드가 호출자에서 제공된 대리자로 하나 이상의 'ref'매개 변수를 전달하는 경우 싱글 톤 또는 정적 대리자를 사용할 수 있으므로 런타임시 클로저 또는 대리자를 만들 필요가 없습니다.
  4. 호텔은 언제 "작업"을하고 있는지 알고 있습니다. 잠금을 유지 한 상태에서 외부 코드를 실행하는 데 항상주의를 기울여야하지만 콜백에서 다른 잠금이 필요할 수있는 작업을 수행하지 않아야하는 발신자를 신뢰할 수있는 경우 메소드가 'CompareExchange'와 호환되지 않는 업데이트는 여전히 원자 적으로 수행 될 수 있습니다.

통과하는 것은 ref훌륭한 패턴입니다. 너무 많이 사용하지 않습니다.


3

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

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제안한대로 전화를 걸 때 전달하는 것은 변수가 아니라 값입니다. 전달하는 값은 getter에서 반환 된 값입니다 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

이 문제에 투표하기 위해 언어에 어떻게 추가 할 수 있는지에 대한 적극적인 제안이 있습니다. 나는 이것이 최선을 다하는 방법이라고 말하는 것이 아니며, 자신의 제안을 자유롭게 내놓을 수 있습니다. 그러나 Visual Basic과 같이 ref가 속성을 전달하도록 허용하면 일부 코드를 단순화하는 데 크게 도움이됩니다.

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.