2015-05-27 11 views
6

Khi tôi nhận kết quả XML deserialized thành cây xsd tạo ra các đối tượng và muốn sử dụng một số đối tượng sâu bên trong cây đó a.b.c.d.e.f, nó sẽ cho tôi ngoại lệ nếu bất kỳ nút nào trên đường dẫn truy vấn đó bị thiếu.Làm thế nào để sử dụng Expression Tree để truy cập một cách an toàn đường dẫn của các đối tượng nullable?

if(a.b.c.d.e.f != null) 
    Console.Write("ok"); 

Tôi muốn tránh kiểm tra cho null cho mỗi cấp độ như thế này:

if(a != null) 
if(a.b != null) 
if(a.b.c != null) 
if(a.b.c.d != null) 
if(a.b.c.d.e != null) 
if(a.b.c.d.e.f != null) 
    Console.Write("ok"); 

giải pháp đầu tiên là để thực hiện Get phương pháp mở rộng cho phép này:

if(a.Get(o=>o.b).Get(o=>o.c).Get(o=>o.d).Get(o=>o.e).Get(o=>o.f) != null) 
    Console.Write("ok"); 

giải pháp thứ hai là triển khai phương thức mở rộng Get (string) và sử dụng phản chiếu để có được kết quả như sau:

if(a.Get("b.c.d.e.f") != null) 
    Console.Write("ok"); 

giải pháp thứ ba, có thể là để thực hiện ExpandoObject và sử dụng loại động để có được kết quả tìm kiếm như thế này:

dynamic da = new SafeExpando(a); 
if(da.b.c.d.e.f != null) 
    Console.Write("ok"); 

Nhưng cuối cùng 2 giải pháp không cho lợi ích của việc gõ mạnh và IntelliSense.

Tôi nghĩ rằng tốt nhất có thể là giải pháp thứ tư có thể được thực hiện với cây biểu:

if(Get(a.b.c.d.e.f) != null) 
    Console.Write("ok"); 

hoặc

if(a.Get(a=>a.b.c.d.e.f) != null) 
    Console.Write("ok"); 

tôi đã thực hiện 1 và giải pháp thứ 2.

Sau đây là cách 1 giải pháp trông giống như:

[DebuggerStepThrough] 
public static To Get<From,To>(this From @this, Func<From,To> get) 
{ 
    var ret = default(To); 
    if(@this != null && [email protected](default(From))) 
     ret = get(@this); 

    if(ret == null && typeof(To).IsArray) 
     ret = (To)Activator.CreateInstance(typeof(To), 0); 

    return ret; 
} 

Làm thế nào để thực hiện giải pháp thứ 4 nếu có thể?

Cũng sẽ rất thú vị khi xem cách triển khai giải pháp thứ 3 nếu có thể.

+0

[Câu hỏi này] (http://stackoverflow.com/questions/3897249/how-to-avoid-multiple-if-null-checks) có hai liên kết đến các câu hỏi có câu trả lời. Một phụ thuộc vào trình biên dịch Roslyn; khác là một đoạn mã đơn giản mà sẽ làm các trick. –

+0

Tôi đọc câu hỏi thực tế và câu trả lời ở trên không trực tiếp giải quyết câu hỏi của bạn nhưng có liên quan và thú vị –

+0

Tôi đã thử nghiệm với điều này gần đây và có thể giải pháp của tôi sẽ xen vào bạn. Tôi đã đăng nó [ở đây] (http://codereview.stackexchange.com/questions/116798/improved-nullguard-v3-that-supports-property-chains-methods-and-ignores-value-t) trong Đánh giá mã. Tôi đã viết thực hiện của riêng tôi bởi vì tôi muốn một cái gì đó nhiều hơn mà chỉ đơn giản là kiểm tra chống lại null ;-) Câu hỏi của bạn và câu trả lời của Servy đã truyền cảm hứng cho tôi để thử một cái gì đó khác nhau. – t3chb0t

Trả lời

12

Vì vậy, địa điểm bắt đầu đang tạo khách truy cập biểu thức. Điều này cho phép chúng tôi tìm tất cả các thành viên truy cập trong một biểu thức cụ thể. Điều này khiến chúng tôi phải đặt câu hỏi về việc phải làm gì cho mỗi thành viên truy cập.

Vì vậy, điều đầu tiên là truy cập đệ quy về biểu thức mà thành viên đang được truy cập. Từ đó, chúng tôi có thể sử dụng Expression.Condition để tạo khối có điều kiện so sánh biểu thức cơ bản đã xử lý đó với null và trả về null nếu đúng biểu thức bắt đầu ban đầu nếu không.

Lưu ý rằng chúng tôi cần cung cấp các triển khai cho cả Thành viên và các cuộc gọi phương thức, nhưng quy trình cho từng cuộc gọi về cơ bản giống nhau.

Chúng tôi cũng sẽ thêm một dấu kiểm để biểu thức bên dưới là null (nghĩa là không có cá thể nào và là thành viên tĩnh) hoặc nếu đó là loại không có giá trị, chúng tôi chỉ sử dụng thay vào đó là hành vi cơ bản.

public class MemberNullPropogationVisitor : ExpressionVisitor 
{ 
    protected override Expression VisitMember(MemberExpression node) 
    { 
     if (node.Expression == null || !IsNullable(node.Expression.Type)) 
      return base.VisitMember(node); 

     var expression = base.Visit(node.Expression); 
     var nullBaseExpression = Expression.Constant(null, expression.Type); 
     var test = Expression.Equal(expression, nullBaseExpression); 
     var memberAccess = Expression.MakeMemberAccess(expression, node.Member); 
     var nullMemberExpression = Expression.Constant(null, node.Type); 
     return Expression.Condition(test, nullMemberExpression, node); 
    } 

    protected override Expression VisitMethodCall(MethodCallExpression node) 
    { 
     if (node.Object == null || !IsNullable(node.Object.Type)) 
      return base.VisitMethodCall(node); 

     var expression = base.Visit(node.Object); 
     var nullBaseExpression = Expression.Constant(null, expression.Type); 
     var test = Expression.Equal(expression, nullBaseExpression); 
     var memberAccess = Expression.Call(expression, node.Method); 
     var nullMemberExpression = Expression.Constant(null, MakeNullable(node.Type)); 
     return Expression.Condition(test, nullMemberExpression, node); 
    } 

    private static Type MakeNullable(Type type) 
    { 
     if (IsNullable(type)) 
      return type; 

     return typeof(Nullable<>).MakeGenericType(type); 
    } 

    private static bool IsNullable(Type type) 
    { 
     if (type.IsClass) 
      return true; 
     return type.IsGenericType && 
      type.GetGenericTypeDefinition() == typeof(Nullable<>); 
    } 
} 

Sau đó chúng tôi có thể tạo ra một phương pháp khuyến nông để thực hiện gọi đó là dễ dàng hơn:

public static Expression PropogateNull(this Expression expression) 
{ 
    return new MemberNullPropogationVisitor().Visit(expression); 
} 

Cũng như một chấp nhận một lambda, chứ không phải bất kỳ biểu hiện, và có thể trả về một đại biểu biên soạn:

public static Func<T> PropogateNull<T>(this Expression<Func<T>> expression) 
{ 
    var defaultValue = Expression.Constant(default(T)); 
    var body = expression.Body.PropogateNull(); 
    if (body.Type != typeof(T)) 
     body = Expression.Coalesce(body, defaultValue); 
    return Expression.Lambda<Func<T>>(body, expression.Parameters) 
     .Compile(); 
} 

Lưu ý rằng, để hỗ trợ các trường hợp thành viên truy cập giải quyết thành giá trị không thể vô hiệu, chúng tôi sẽ thay đổi loại biểu thức đó để vô hiệu hóa chúng, sử dụng MakeNullable. Đây là vấn đề với biểu thức cuối cùng này, vì nó cần phải là Func<T> và nó sẽ không khớp nếu T cũng không được dỡ bỏ. Do đó, trong khi nó không lý tưởng lắm (lý tưởng là bạn không bao giờ gọi phương thức này với một giá trị mặc định là, nhưng không có cách nào tốt để hỗ trợ điều này trong C#), chúng ta kết hợp giá trị cuối cùng bằng cách sử dụng giá trị mặc định cho kiểu đó, nếu cần thiết.

(Bạn trivially có thể sửa đổi này phải chấp nhận một lambda chấp nhận một tham số, và vượt qua trong một giá trị, nhưng bạn có thể cũng giống như dễ dàng gần gũi hơn thông số mà thay vào đó, vì vậy tôi thấy không có lý do thực sự tới.)


Nó cũng đáng để chỉ ra rằng trong C# 6.0, khi nó thực sự được phát hành, chúng ta sẽ có một toán tử vô giá trị thực tế (?.), làm cho tất cả điều này rất không cần thiết. Bạn sẽ có thể viết:

if(a?.b?.c?.d?.e?.f != null) 
    Console.Write("ok"); 

và có chính xác ngữ nghĩa bạn đang tìm kiếm.

+0

Expression.Constant (null, node.Type) cho ngoại lệ loại giá trị tăng, thay thế null với giá trị mặc định của loại (http://stackoverflow.com/a/2490274/440030) giải quyết vấn đề. –

+0

@RezaArabQaeni Đó không thực sự là ngữ nghĩa thích hợp cho một toán tử tuyên truyền vô giá trị. Nó sẽ nâng kết quả lên một kiểu nullable, nhưng những hạn chế của ngôn ngữ không thực sự cho phép điều đó xảy ra trong mọi trường hợp, chỉ những cái nhất định mới có thể được hỗ trợ. Cụ thể, nếu biểu thức cuối cùng được dỡ bỏ thì nó sẽ không còn khớp với chữ ký của người được ủy nhiệm, và việc thay đổi đó thực sự vượt quá những gì C# có thể hỗ trợ hiệu quả. Sử dụng giá trị mặc định trong một trường hợp đó ít nhất là tốt hơn luôn luôn sử dụng nó, tôi đoán, vì vậy tôi đã làm điều đó cho bây giờ. – Servy

+0

Ok, bạn nói đúng, Vậy ý tưởng của bạn về thời điểm loại nút là loại giá trị thay vì Expression.Condition (để kiểm tra thuộc tính là null) trả về một biểu thức đơn giản mà không kiểm tra điều kiện rỗng, mặc dù tôi không biết triển khai thực hiện biểu thức đơn giản này. –

Các vấn đề liên quan