Sử dụng phản xạ trong C # để lấy thuộc tính của một đối tượng lồng nhau


82

Cho các đối tượng sau:

public class Customer {
    public String Name { get; set; }
    public String Address { get; set; }
}

public class Invoice {
    public String ID { get; set; }
    public DateTime Date { get; set; }
    public Customer BillTo { get; set; }
}

Tôi muốn sử dụng sự phản chiếu để đi qua Invoiceđể lấy thuộc Nametính của a Customer. Đây là những gì tôi đang theo đuổi, giả sử mã này sẽ hoạt động:

Invoice inv = GetDesiredInvoice();  // magic method to get an invoice
PropertyInfo info = inv.GetType().GetProperty("BillTo.Address");
Object val = info.GetValue(inv, null);

Tất nhiên, điều này không thành công vì "BillTo.Address" không phải là thuộc tính hợp lệ của Invoicelớp.

Vì vậy, tôi đã thử viết một phương thức để chia chuỗi thành nhiều phần trong khoảng thời gian và đi dọc các đối tượng để tìm kiếm giá trị cuối cùng mà tôi quan tâm. Nó hoạt động ổn, nhưng tôi không hoàn toàn thoải mái với nó:

public Object GetPropValue(String name, Object obj) {
    foreach (String part in name.Split('.')) {
        if (obj == null) { return null; }

        Type type = obj.GetType();
        PropertyInfo info = type.GetProperty(part);
        if (info == null) { return null; }

        obj = info.GetValue(obj, null);
    }
    return obj;
}

Bất kỳ ý tưởng về cách cải thiện phương pháp này, hoặc cách tốt hơn để giải quyết vấn đề này?

CHỈNH SỬA sau khi đăng, tôi thấy một vài bài viết liên quan ... Tuy nhiên, dường như không có câu trả lời nào giải quyết cụ thể câu hỏi này. Ngoài ra, tôi vẫn muốn phản hồi về việc triển khai của tôi.


chỉ tò mò, nếu bạn GetDesiredInvoicetrả về cho bạn một đối tượng của loại Invoicetại sao không sử dụng inv.BillTo.Nametrực tiếp?
ram

Tôi thực sự sử dụng điều này hơi khác một chút, chỉ đơn giản hóa cho ví dụ của tôi. Tôi đang lấy một đối tượng và chuyển nó vào một bộ xử lý để kết hợp nó với một mẫu để in.
jheddings

Nó chỉ cảm thấy một chút "bạo lực" và có vẻ như nên có một cách tốt hơn. Tuy nhiên, từ các câu trả lời cho đến nay, có vẻ như tôi không hoàn toàn thiếu cơ sở.
jheddings

3
Tôi đã nghĩ rằng mình thật điên rồ khi làm điều này, nhưng, có vẻ như ai đó đã gặp phải vấn đề tương tự như tôi. Lớn giải pháp bằng cách này
Marcello Grechi Lins

Ngoài ra, hãy kiểm tra câu trả lời của tôi cho một chủ đề khác để sử dụng chúng làm phương pháp mở rộng: stackoverflow.com/questions/1196991/…
jheddings

Câu trả lời:


12

Tôi thực sự nghĩ rằng logic của bạn là tốt. Cá nhân, tôi có thể sẽ thay đổi nó xung quanh để bạn chuyển đối tượng làm tham số đầu tiên (nội dòng hơn với PropertyInfo.GetValue, vì vậy ít ngạc nhiên hơn).

Tôi cũng có thể gọi nó giống như GetNestedPropertyValue hơn, để làm cho nó rõ ràng là nó tìm kiếm trong ngăn xếp thuộc tính.


Chúc bạn sắp xếp lại các thông số và thay đổi tên được đề xuất.
itowlson

Cảm ơn bạn đã phản hồi ... Tôi đã thực hiện cả hai đề xuất. Cuối cùng tôi đã biến nó thành một phương thức mở rộng trên Objectlớp, điều này củng cố quan điểm về việc sắp xếp lại các tham số.
jheddings

Tại sao câu trả lời được chấp nhận? Nó không giúp tôi lấy thuộc tính của một đối tượng lồng nhau như OP đã hỏi.
Levitikon

@Levitikon Bộ mã thứ hai của OP cho thấy một cách tốt để thực hiện những gì được yêu cầu. Đó là toàn bộ quan điểm của tôi. Không có gì sai với mã như được đăng trong chính câu trả lời.
Reed Copsey

27

Tôi sử dụng phương thức sau để lấy các giá trị từ các thuộc tính (các lớp lồng nhau) như

"Bất động sản"

"Address.Street"

"Address.Country.Name"

    public static object GetPropertyValue(object src, string propName)
    {
        if (src == null) throw new ArgumentException("Value cannot be null.", "src");
        if (propName == null) throw new ArgumentException("Value cannot be null.", "propName");

        if(propName.Contains("."))//complex type nested
        {
            var temp = propName.Split(new char[] { '.' }, 2);
            return GetPropertyValue(GetPropertyValue(src, temp[0]), temp[1]);
        }
        else
        {
            var prop = src.GetType().GetProperty(propName);
            return prop != null ? prop.GetValue(src, null) : null;
        }
    }

Đây là Fiddle: https://dotnetfiddle.net/PvKRH0


Nếu thuộc tính là null, điều này sẽ không hoạt động, cần phải kiểm tra xem src có phải là null không trước khi làm việc với.
Furtiro

@Furtiro vâng, chắc chắn, không thể hoạt động cho dù src (hoặc propName) là null. Tôi đã thêm ngoại lệ ném. Cảm ơn
DevT

Rất vui được giúp đỡ! Nhưng nó không hoạt động với tôi với tài sản ba lồng, nó dừng lại sau 2, buồn nhưng tuyệt vời mã!
Furtiro

@Furtiro thật lạ, phải hoạt động như bạn có thể thấy trên fiddle mà tôi vừa thêm vào bài đăng. Hãy xem, có thể bạn có thể tìm thấy vấn đề của mình.
DevT

@DevT Xin chào, Bạn có cùng cách tiếp cận để làm cho GetProperty hoạt động khi chúng ta sử dụng lớp lồng nhau không? tức là var property = type.GetProperty (sortProperty); không thành công với lớp lồng nhau (vì chúng tôi nhận được kết quả rỗng), tôi cảm thấy giải pháp của bạn có thể trả lời điều này. (Để biết chi tiết đầy đủ, giải pháp được đưa ra ở đây stackoverflow.com/questions/11336713/… không thành công với lớp lồng nhau)
Kynao

13

Tôi biết mình đến bữa tiệc hơi muộn và như những người khác đã nói, việc triển khai của bạn vẫn ổn
... đối với các trường hợp sử dụng đơn giản .
Tuy nhiên, tôi đã phát triển một thư viện giải quyết chính xác trường hợp sử dụng đó, Pather.CSharp .
Nó cũng có sẵn dưới dạng Gói Nuget .

Lớp chính của nó là Resolvervới Resolvephương thức của nó .
Bạn chuyển cho nó một đối tượng và đường dẫn thuộc tính , và nó sẽ trả về giá trị mong muốn .

Invoice inv = GetDesiredInvoice();  // magic method to get an invoice
var resolver = new Resolver();
object result = resolver.Resolve(inv, "BillTo.Address");

Nhưng nó cũng có thể giải quyết các đường dẫn thuộc tính phức tạp hơn , bao gồm truy cập mảng và từ điển.
Vì vậy, ví dụ: nếu bạn Customernhiều địa chỉ

public class Customer {
    public String Name { get; set; }
    public IEnumerable<String> Addresses { get; set; }
}

bạn có thể truy cập cái thứ hai bằng cách sử dụng Addresses[1].

Invoice inv = GetDesiredInvoice();  // magic method to get an invoice
var resolver = new Resolver();
object result = resolver.Resolve(inv, "BillTo.Addresses[1]");

2
Làm thế nào để điều này xử lý các đối tượng null cho các thuộc tính lồng nhau, ví dụ như NullObject.Id với NullObject là null trên Hóa đơn?
AVFC_Bubble88

10

Bạn phải truy cập đối tượng ACTUAL mà bạn cần sử dụng phản chiếu. Đây là những gì tôi muốn nói:

Thay vì điều này:

Invoice inv = GetDesiredInvoice();  // magic method to get an invoice
PropertyInfo info = inv.GetType().GetProperty("BillTo.Address");
Object val = info.GetValue(inv, null);

Làm điều này (chỉnh sửa dựa trên nhận xét):

Invoice inv = GetDesiredInvoice();  // magic method to get an invoice
PropertyInfo info = inv.GetType().GetProperty("BillTo");
Customer cust = (Customer)info.GetValue(inv, null);

PropertyInfo info2 = cust.GetType().GetProperty("Address");
Object val = info2.GetValue(cust, null);

Xem bài đăng này để biết thêm thông tin: Sử dụng phản chiếu để đặt thuộc tính của thuộc tính của một đối tượng


Cảm ơn câu trả lời ... Tôi biết cách lấy giá trị của thuộc tính cấp một, nhưng tôi đang tự hỏi làm thế nào để lấy giá trị của thuộc tính lồng nhau. Trong ứng dụng thực tế của tôi, tôi không có quyền truy cập vào đối tượng thực tế.
jheddings

1
Cần lấy trực tiếp các thuộc tính lồng nhau. Tôi cũng đang ở trong tình huống mà "Invoice" là T và tôi có một chuỗi đường dẫn "Property.Property.Property". Không thể loanh quanh với từng tài sản.
Levitikon

Điều này đã làm điều đó cho tôi. Tôi cũng không gặp may với BillTo.Address. Tôi tò mò liệu việc đặt thuộc tính có hoạt động theo cùng một cách không?
Yusif_Nurizade

7

Với hy vọng không quá muộn cho bữa tiệc, tôi muốn bổ sung giải pháp của mình: Chắc chắn sử dụng đệ quy trong tình huống này

public static Object GetPropValue(String name, object obj, Type type)
    {
        var parts = name.Split('.').ToList();
        var currentPart = parts[0];
        PropertyInfo info = type.GetProperty(currentPart);
        if (info == null) { return null; }
        if (name.IndexOf(".") > -1)
        {
            parts.Remove(currentPart);
            return GetPropValue(String.Join(".", parts), info.GetValue(obj, null), info.PropertyType);
        } else
        {
            return info.GetValue(obj, null).ToString();
        }
    }

6

Bạn không giải thích nguồn gốc của "sự khó chịu" của bạn, nhưng về cơ bản mã của bạn trông có vẻ hợp lý với tôi.

Điều duy nhất tôi thắc mắc là xử lý lỗi. Bạn trả về null nếu mã cố gắng duyệt qua tham chiếu null hoặc nếu tên thuộc tính không tồn tại. Điều này ẩn các lỗi: thật khó để biết liệu nó có trả về giá trị không vì không có khách hàng BillTo nào, hoặc do bạn viết sai chính tả "BilTo.Address" ... hoặc vì có một khách hàng BillTo và Địa chỉ của nó là null! Tôi sẽ để phương thức bị lỗi và ghi trong những trường hợp này - chỉ cần để ngoại lệ thoát (hoặc có thể bọc nó trong một phương thức thân thiện hơn).


3

Đây là một triển khai khác sẽ bỏ qua một thuộc tính lồng nhau nếu nó là một điều tra viên và tiếp tục sâu hơn. Thuộc tính của kiểu chuỗi không bị ảnh hưởng bởi Kiểm tra liệt kê.

public static class ReflectionMethods
{
    public static bool IsNonStringEnumerable(this PropertyInfo pi)
    {
        return pi != null && pi.PropertyType.IsNonStringEnumerable();
    }

    public static bool IsNonStringEnumerable(this object instance)
    {
        return instance != null && instance.GetType().IsNonStringEnumerable();
    }

    public static bool IsNonStringEnumerable(this Type type)
    {
        if (type == null || type == typeof(string))
            return false;
        return typeof(IEnumerable).IsAssignableFrom(type);
    }

    public static Object GetPropValue(String name, Object obj)
    {
        foreach (String part in name.Split('.'))
        {
            if (obj == null) { return null; }
            if (obj.IsNonStringEnumerable())
            {
                var toEnumerable = (IEnumerable)obj;
                var iterator = toEnumerable.GetEnumerator();
                if (!iterator.MoveNext())
                {
                    return null;
                }
                obj = iterator.Current;
            }
            Type type = obj.GetType();
            PropertyInfo info = type.GetProperty(part);
            if (info == null) { return null; }

            obj = info.GetValue(obj, null);
        }
        return obj;
    }
}

dựa trên câu hỏi này và trên

Làm thế nào để biết một PropertyInfo có phải là một tập hợp của Berryl hay không

Tôi sử dụng điều này trong một dự án MVC để sắp xếp động dữ liệu của tôi bằng cách chỉ cần chuyển Thuộc tính để sắp xếp theo Ví dụ:

result = result.OrderBy((s) =>
                {
                    return ReflectionMethods.GetPropValue("BookingItems.EventId", s);
                }).ToList();

trong đó BookingItems là danh sách các đối tượng.


2
> Get Nest properties e.g., Developer.Project.Name
private static System.Reflection.PropertyInfo GetProperty(object t, string PropertName)
            {
                if (t.GetType().GetProperties().Count(p => p.Name == PropertName.Split('.')[0]) == 0)
                    throw new ArgumentNullException(string.Format("Property {0}, is not exists in object {1}", PropertName, t.ToString()));
                if (PropertName.Split('.').Length == 1)
                    return t.GetType().GetProperty(PropertName);
                else
                    return GetProperty(t.GetType().GetProperty(PropertName.Split('.')[0]).GetValue(t, null), PropertName.Split('.')[1]);
            }

1
   if (info == null) { /* throw exception instead*/ } 

Tôi thực sự sẽ ném một ngoại lệ nếu họ yêu cầu một thuộc tính không tồn tại. Theo cách bạn mã hóa nó, nếu tôi gọi GetPropValue và nó trả về null, tôi không biết liệu điều đó có nghĩa là thuộc tính không tồn tại hay thuộc tính đã tồn tại nhưng giá trị của nó là null.


Ngoài ra, di chuyển kiểm tra đối với obj là null ra bên ngoài vòng lặp.
Kevin Brock

Xin lỗi, không thấy việc sử dụng obj nhiều lần. Việc thay đổi các tham số của bạn là không tốt, điều này có thể dẫn đến nhầm lẫn trong tương lai. Sử dụng một biến khác cho tham số obj để di chuyển trong vòng lặp.
Kevin Brock

Kevin: Để sử dụng một biến khác, anh ấy phải gán nó cho obj ở cuối, hoặc làm cho phương thức đệ quy. Cá nhân, tôi không nghĩ rằng đây là một vấn đề (mặc dù một lời nhận xét tốt sẽ được tốt đẹp ...)
Reed Copsey

1
@Levitikon OP đã nêu "Bất kỳ ý tưởng nào về cách cải thiện phương pháp này hoặc cách tốt hơn để giải quyết vấn đề này?". Vì vậy, đây là một câu trả lời chứ không phải một bình luận, bởi vì OP đã yêu cầu cải tiến, đây là một cải tiến.
AaronLS

1
    public static string GetObjectPropertyValue(object obj, string propertyName)
    {
        bool propertyHasDot = propertyName.IndexOf(".") > -1;
        string firstPartBeforeDot;
        string nextParts = "";

        if (!propertyHasDot)
            firstPartBeforeDot = propertyName.ToLower();
        else
        {
            firstPartBeforeDot = propertyName.Substring(0, propertyName.IndexOf(".")).ToLower();
            nextParts = propertyName.Substring(propertyName.IndexOf(".") + 1);
        }

        foreach (var property in obj.GetType().GetProperties())
            if (property.Name.ToLower() == firstPartBeforeDot)
                if (!propertyHasDot)
                    if (property.GetValue(obj, null) != null)
                        return property.GetValue(obj, null).ToString();
                    else
                        return DefaultValue(property.GetValue(obj, null), propertyName).ToString();
                else
                    return GetObjectPropertyValue(property.GetValue(obj, null), nextParts);
        throw new Exception("Property '" + propertyName.ToString() + "' not found in object '" + obj.ToString() + "'");
    }

1
Mô tả về sự thất vọng của bạn dựa trên điều gì, nó sẽ giúp OP như thế nào là một hành động tốt.
DontVoteMeDown

0

Kết nối internet của tôi bị ngắt khi tôi cần giải quyết vấn đề tương tự, vì vậy tôi phải 'phát minh lại bánh xe':

static object GetPropertyValue(Object fromObject, string propertyName)
{
    Type objectType = fromObject.GetType();
    PropertyInfo propInfo = objectType.GetProperty(propertyName);
    if (propInfo == null && propertyName.Contains('.'))
    {
        string firstProp = propertyName.Substring(0, propertyName.IndexOf('.'));
        propInfo = objectType.GetProperty(firstProp);
        if (propInfo == null)//property name is invalid
        {
            throw new ArgumentException(String.Format("Property {0} is not a valid property of {1}.", firstProp, fromObject.GetType().ToString()));
        }
        return GetPropertyValue(propInfo.GetValue(fromObject, null), propertyName.Substring(propertyName.IndexOf('.') + 1));
    }
    else
    {
        return propInfo.GetValue(fromObject, null);
    }
}

Khá chắc chắn rằng điều này giải quyết vấn đề cho bất kỳ chuỗi nào bạn sử dụng cho tên thuộc tính, bất kể mức độ lồng nhau, miễn là mọi thứ đều là thuộc tính.


0

Tôi muốn chia sẻ giải pháp của mình mặc dù có thể đã quá muộn. Giải pháp này chủ yếu để kiểm tra xem thuộc tính lồng nhau có tồn tại hay không. Nhưng có thể dễ dàng tinh chỉnh để trả về giá trị thuộc tính nếu cần.

private static PropertyInfo _GetPropertyInfo(Type type, string propertyName)
        {
            //***
            //*** Check if the property name is a complex nested type
            //***
            if (propertyName.Contains("."))
            {
                //***
                //*** Get the first property name of the complex type
                //***
                var tempPropertyName = propertyName.Split(".", 2);
                //***
                //*** Check if the property exists in the type
                //***
                var prop = _GetPropertyInfo(type, tempPropertyName[0]);
                if (prop != null)
                {
                    //***
                    //*** Drill down to check if the nested property exists in the complex type
                    //***
                    return _GetPropertyInfo(prop.PropertyType, tempPropertyName[1]);
                }
                else
                {
                    return null;
                }
            }
            else
            {
                return type.GetProperty(propertyName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
            }
        }

Tôi đã phải tham khảo một số bài viết để đưa ra giải pháp này. Tôi nghĩ rằng điều này sẽ hoạt động đối với nhiều loại thuộc tính lồng nhau.


-7

Thử inv.GetType().GetProperty("BillTo+Address");

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.