Truyền thuộc tính bằng cách tham chiếu trong C #


224

Tôi đang cố gắng làm như sau:

GetString(
    inputString,
    ref Client.WorkPhone)

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

Đây là một lỗi biên dịch. Tôi nghĩ nó khá rõ ràng những gì tôi đang cố gắng để đạt được. Về cơ bản tôi muốn GetStringsao chép nội dung của một chuỗi đầu vào vào thuộc WorkPhonetính của Client.

Có thể vượt qua một tài sản bằng cách tham khảo?


Để biết lý do tại sao, hãy xem stackoverflow.com/questions/564557/
nawfal

Câu trả lời:


423

Thuộc tính không thể được thông qua tham chiếu. Dưới đây là một vài cách bạn có thể làm việc xung quanh giới hạn này.

1. Giá trị trả về

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. Đại biểu

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. Biểu thức 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. Suy tư

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
Yêu các ví dụ. Tôi thấy rằng đây cũng là một nơi tuyệt vời cho các phương thức mở rộng: codechuỗi tĩnh công khai GetValueOrDefault (chuỗi này s, chuỗi isNullString) {if (s == null) {s = isNullString; } trả về s; } void Main () {person.MobilePhone.GetValueOrDefault (person.WorkPhone); }
BlackjquetMack

9
Trong giải pháp 2, tham số thứ 2 getOutputlà không cần thiết.
Jaider

31
Và tôi nghĩ một cái tên tốt hơn cho giải pháp 3 là Reflection.
Jaider

1
Trong giải pháp 2, tham số thứ 2 getOutput là không cần thiết - đúng nhưng tôi đã sử dụng nó trong GetString để xem giá trị mà tôi đã đặt là gì. Không chắc chắn làm thế nào để làm điều đó mà không có tham số này.
Petras

3
@GoneCodingoodbye: nhưng cách tiếp cận kém hiệu quả nhất. Sử dụng sự phản chiếu để chỉ định một giá trị cho một thuộc tính cũng giống như dùng búa tạ để bẻ đai ốc. Ngoài ra, một phương pháp GetStringđược cho là đặt thuộc tính rõ ràng được đặt tên sai.
Tim Schmelter

27

không trùng lặp tài sản

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
+1 để thay đổi tên GetStringthành NullSafeSet, bởi vì trước đây không có ý nghĩa ở đây.
Camilo Martin

25

Tôi đã viết một trình bao bọc bằng biến thể ExpressionTree và c # 7 (nếu có ai đó quan tâm):

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

Và sử dụng nó như:

var accessor = new Accessor<string>(() => myClient.WorkPhone);
accessor.Set("12345");
Assert.Equal(accessor.Get(), "12345");

3
Câu trả lời tốt nhất ở đây. Bạn có biết tác động hiệu suất là gì? Nó sẽ được tốt đẹp để có nó trong câu trả lời. Tôi không quen thuộc với các cây biểu thức nhưng tôi mong đợi rằng việc sử dụng Compile () có nghĩa là cá thể của trình truy cập chứa mã được biên dịch IL thực sự và do đó sử dụng số lượng truy cập không đổi n lần sẽ ổn, nhưng sử dụng tổng số n truy cập ( chi phí ctor cao) sẽ không.
mancze

Mã tuyệt vời! Ý kiến ​​của tôi, nó là câu trả lời tốt nhất. Một cái chung nhất. Giống như nói mancze ... Nó sẽ có tác động rất lớn đến hiệu suất và chỉ nên được sử dụng trong bối cảnh mà độ rõ của mã quan trọng hơn độ hoàn hảo.
Eric Ouellet

5

Nếu bạn muốn lấy và đặt thuộc tính cả hai, bạn có thể sử dụng thuộc tính này trong 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

Một mẹo khác chưa được đề cập là để lớp thực hiện một thuộc tính (ví dụ như Fookiểu Bar) cũng xác định một ủy nhiệm delegate void ActByRef<T1,T2>(ref T1 p1, ref T2 p2);và thực hiện một phương thức ActOnFoo<TX1>(ref Bar it, ActByRef<Bar,TX1> proc, ref TX1 extraParam1)(và có thể là các phiên bản cho hai và ba "tham số bổ sung") cũng sẽ chuyển đại diện bên trong của nó Foocho các thủ tục được cung cấp như là một reftham số. Điều này có một vài lợi thế lớn so với các phương pháp làm việc với tài sản khác:

  1. Tài sản được cập nhật "tại chỗ"; nếu thuộc tính thuộc loại tương thích với các phương thức `Interlocked` hoặc nếu nó là một cấu trúc với các trường được phơi bày của các loại đó, thì các phương thức` Interlocked` có thể được sử dụng để thực hiện cập nhật nguyên tử cho thuộc tính.
  2. Nếu thuộc tính là một cấu trúc trường tiếp xúc, các trường của cấu trúc có thể được sửa đổi mà không phải tạo bất kỳ bản sao dự phòng nào của nó.
  3. Nếu phương thức `ActByRef` truyền một hoặc nhiều tham số` ref` thông qua từ người gọi đến đại biểu được cung cấp, có thể sử dụng một đại biểu đơn hoặc tĩnh, do đó tránh được yêu cầu tạo các bao đóng hoặc ủy nhiệm trong thời gian chạy.
  4. Khách sạn biết khi nào nó được "làm việc với". Mặc dù luôn luôn phải sử dụng thận trọng khi thực thi mã bên ngoài trong khi giữ khóa, nhưng nếu người ta có thể tin tưởng người gọi không làm quá bất cứ điều gì trong cuộc gọi lại có thể yêu cầu khóa khác, thì có thể thực tế là phương thức bảo vệ quyền truy cập thuộc tính bằng một khóa, sao cho các cập nhật không tương thích với `So sánhExchange` vẫn có thể được thực hiện gần như nguyên tử.

Vượt qua mọi thứ reflà một mô hình xuất sắc; quá tệ, nó không được sử dụng nhiều hơn.


3

Chỉ cần mở rộng một chút cho giải pháp Linq Expression của Nathan . Sử dụng đa chung param để thuộc tính không giới hạn chuỗi.

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

Điều này được đề cập trong phần 7.4.1 của thông số ngôn ngữ C #. Chỉ một tham chiếu biến có thể được truyền dưới dạng tham số ref hoặc out trong danh sách đối số. Một thuộc tính không đủ điều kiện làm tham chiếu biến và do đó không thể được sử dụng.


2

Điều này là không thể. Bạn có thể nói

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

nơi WorkPhonelà một khả năng ghi stringtài sản và định nghĩa về GetStringđược thay đổi để

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

Điều này sẽ có cùng ngữ nghĩa mà bạn dường như đang cố gắng.

Điều này là không thể bởi vì một tài sản thực sự là một cặp phương thức được ngụy trang. Mỗi thuộc tính làm cho các getters và setters có sẵn có thể truy cập thông qua cú pháp giống như trường. Khi bạn cố gắng gọi GetStringnhư bạn đã đề xuất, những gì bạn chuyển đến là một giá trị chứ không phải là một biến. Giá trị mà bạn đang truyền vào là trả về từ getter get_WorkPhone.


1

Những gì bạn có thể cố gắng làm là tạo một đối tượng để giữ giá trị thuộc tính. Bằng cách đó bạn có thể vượt qua đối tượng và vẫn có quyền truy cập vào tài sản bên trong.


1

Thuộc tính không thể được thông qua tham chiếu? Biến nó thành một trường sau đó và sử dụng thuộc tính để tham chiếu công khai:

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

Bạn không thể reflà một thuộc tính, nhưng nếu các hàm của bạn cần cả hai getsettruy cập, bạn có thể chuyển qua một thể hiện của một lớp có thuộc tính được xác định:

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

Thí dụ:

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

Câu trả lời được chấp nhận là tốt nếu chức năng đó nằm trong mã của bạn và bạn có thể sửa đổi nó. Nhưng đôi khi bạn phải sử dụng một đối tượng và hàm từ một thư viện bên ngoài và bạn không thể thay đổi định nghĩa thuộc tính và hàm. Sau đó, bạn chỉ có thể sử dụng một biến tạm thời.

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

0

Để bỏ phiếu về vấn đề này, đây là một gợi ý tích cực về cách có thể thêm ngôn ngữ này vào ngôn ngữ. Tôi không nói rằng đây là cách tốt nhất để làm điều này (tất cả), hãy thoải mái đưa ra đề nghị của riêng bạn. Nhưng việc cho phép các thuộc tính được thông qua bởi ref như Visual Basic đã có thể giúp cực kỳ đơn giản hóa một số mã và khá thường xuyên!

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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.