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.
[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. –
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ị –
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