2012-03-19 30 views
11

Tôi có một bộ công cụ có nhiều phương pháp thường lấy Expression<Func<T,TProperty>> làm tham số. Một số có thể là một cấp duy nhất (o=>o.Name), trong khi một số có thể là đa cấp (o=>o.EmployeeData.Address.Street).Roslyn có phải là công cụ thích hợp để kiểm tra biểu thức thời gian biên dịch không?

Tôi muốn phát triển thứ gì đó (MSBuild Task? Visual Studio Plugin? Hy vọng đầu tiên) đọc tất cả tệp .cs của người dùng và cung cấp lỗi xây dựng nếu tham số đã cho không phải là biểu thức thuộc tính (nhưng giống như o=>o.Contains("foo")) , hoặc nếu một biểu thức đa cấp được đưa ra, nơi chỉ cho phép một cấp đơn.

Tôi đã thử xem mã IL đã biên dịch trước nhưng vì biểu thức cây là thủ thuật "#", trong IL tất cả những gì tôi thấy là tạo các thể hiện biểu thức và như vậy, và trong khi tôi có thể kiểm tra mỗi chỉ MemberExpressions (và số lượng chính xác của chúng) được tạo ra, nó không phải là tuyệt vời như vậy.

Sau đó, Roslyn đến với tôi. Có thể viết một cái gì đó như thế này với Roslyn?

+0

Tại sao bạn cần thực thi những ràng buộc này? –

+0

vì những thứ tôi làm trong các phương pháp này (xử lý thay đổi thuộc tính, kiểm tra lỗi, vv) chỉ có ý nghĩa đối với biểu thức đặc tính – TDaver

+3

và bởi vì điều này giống như một điều thú vị để làm :) – TDaver

Trả lời

11

Vâng, tôi nghĩ Roslyn và các vấn đề về mã của nó chính xác là công cụ thích hợp cho việc này. Với chúng, bạn có thể phân tích mã khi bạn nhập và tạo lỗi (hoặc cảnh báo) được hiển thị dưới dạng các lỗi khác trong Visual Studio.

Tôi đã cố gắng để tạo mã vấn đề như:

[ExportSyntaxNodeCodeIssueProvider("PropertyExpressionCodeIssue", LanguageNames.CSharp, typeof(InvocationExpressionSyntax))] 
class PropertyExpressionCodeIssueProvider : ICodeIssueProvider 
{ 
    [ImportingConstructor] 
    public PropertyExpressionCodeIssueProvider() 
    {} 

    public IEnumerable<CodeIssue> GetIssues(IDocument document, CommonSyntaxNode node, CancellationToken cancellationToken) 
    { 
     var invocation = (InvocationExpressionSyntax)node; 

     var semanticModel = document.GetSemanticModel(cancellationToken); 

     var semanticInfo = semanticModel.GetSemanticInfo(invocation, cancellationToken); 

     var methodSymbol = (MethodSymbol)semanticInfo.Symbol; 

     if (methodSymbol == null) 
      yield break; 

     var attributes = methodSymbol.GetAttributes(); 

     if (!attributes.Any(a => a.AttributeClass.Name == "PropertyExpressionAttribute")) 
      yield break; 

     var arguments = invocation.ArgumentList.Arguments; 
     foreach (var argument in arguments) 
     { 
      var lambdaExpression = argument.Expression as SimpleLambdaExpressionSyntax; 
      if (lambdaExpression == null) 
       continue; 

      var parameter = lambdaExpression.Parameter; 
      var memberAccess = lambdaExpression.Body as MemberAccessExpressionSyntax; 
      if (memberAccess != null) 
      { 
       var objectIdentifierSyntax = memberAccess.Expression as IdentifierNameSyntax; 

       if (objectIdentifierSyntax != null 
        && objectIdentifierSyntax.PlainName == parameter.Identifier.ValueText 
        && semanticModel.GetSemanticInfo(memberAccess, cancellationToken).Symbol is PropertySymbol) 
        continue; 
      } 

      yield return 
       new CodeIssue(
        CodeIssue.Severity.Error, argument.Span, 
        string.Format("Has to be simple property access of '{0}'", parameter.Identifier.ValueText)); 
     } 
    } 

    #region Unimplemented ICodeIssueProvider members 

    public IEnumerable<CodeIssue> GetIssues(IDocument document, CommonSyntaxToken token, CancellationToken cancellationToken) 
    { 
     throw new NotImplementedException(); 
    } 

    public IEnumerable<CodeIssue> GetIssues(IDocument document, CommonSyntaxTrivia trivia, CancellationToken cancellationToken) 
    { 
     throw new NotImplementedException(); 
    } 

    #endregion 
} 

Việc sử dụng sẽ là như thế này:

[AttributeUsage(AttributeTargets.Method)] 
class PropertyExpressionAttribute : Attribute 
{ } 

… 

[PropertyExpression] 
static void Foo<T>(Expression<Func<SomeType, T>> expr) 
{ } 

… 

Foo(x => x.P); // OK 
Foo(x => x.M()); // error 
Foo(x => 42); // error 

Đoạn mã trên có một số vấn đề:

  1. Nó hoàn toàn được tối ưu hóa .
  2. Có thể cần kiểm tra lỗi nhiều hơn.
  3. Nó không hoạt động. Ít nhất trong CTP hiện tại. Biểu thức semanticModel.GetSemanticInfo(memberAccess, cancellationToken).Symbol gần cuối luôn trả về null. Điều này là do ngữ nghĩa của cây biểu thức nằm trong số the currently unimplemented features.
+3

bạn có viết bài tập về nhà không? : D – TDaver

+0

Roslyn ra mắt với một CTP mới. Tôi đoán đó là thời điểm tốt để thử lại lần nữa? :) – TDaver

5

Có, điều đó hoàn toàn có thể. Vấn đề là Roslyn chưa hỗ trợ tất cả các cấu trúc ngôn ngữ, vì vậy bạn có thể gặp phải một số nội dung không được hỗ trợ. Cây biểu hiện không được hỗ trợ trong đó Roslyn không thể biên dịch mã tạo ra các biểu thức, nhưng bạn sẽ có thể nhận được đủ xa để làm cho một số thứ hoạt động.

Ở mức cao, nếu bạn muốn triển khai tác vụ này dưới dạng tác vụ MSBuild, trong tác vụ xây dựng, bạn có thể gọi Roslyn.Services.Workspace.LoadSolution hoặc Roslyn.Services.Workspace.LoadStandaloneProject. Sau đó, bạn sẽ đi qua các cây cú pháp tìm kiếm các đề cập về các phương thức khác nhau của bạn, và sau đó ràng buộc chúng để đảm bảo nó thực sự là phương pháp bạn nghĩ bạn đang gọi. Từ đó, bạn có thể tìm thấy các nút cú pháp lambda và thực hiện bất kỳ cú pháp/phân tích ngữ nghĩa nào bạn muốn từ đó.

Có một vài dự án mẫu trong CTP bạn có thể thấy hữu ích, chẳng hạn như dự án RFxCopConsoleCS, triển khai quy tắc kiểu FxCop đơn giản trong Roslyn.

Tôi cũng nên đề cập rằng trình phân tích cú pháp hoàn tất cho Roslyn, vì vậy bạn càng có thể thực hiện mà không có thông tin ngữ nghĩa thì càng tốt.

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