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귀하의 기능이 모두 필요한 경우 속성,하지만 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);
    }
}

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.