2013-08-01 32 views
16

Tôi đang tạo một phần mềm trong C#. Tôi đang sử dụng một lớp trừu tượng, Instruction, có những bit mã:Giải quyết sự cố 'Gọi phương thức ảo trong sự cố của người xây dựng'

protected Instruction(InstructionSet instructionSet, ExpressionElement newArgument, 
    bool newDoesUseArgument, int newDefaultArgument, int newCostInBytes, bool newDoesUseRealInstruction) { 

    //Some stuff 

    if (DoesUseRealInstruction) { 
     //The warning appears here. 
     RealInstruction = GetRealInstruction(instructionSet, Argument); 
    } 
} 

public virtual Instruction GetRealInstruction(InstructionSet instructionSet, ExpressionElement argument) { 
    throw new NotImplementedException("Real instruction not implemented. Instruction type: " + GetType()); 
} 

Vì vậy Resharper nói với tôi rằng tại dòng rõ rệt Tôi 'gọi một phương thức ảo trong constructor' và điều này là xấu. Tôi hiểu điều về thứ tự mà các nhà thầu được gọi. Tất cả các ghi đè của phương pháp GetRealInstruction trông như thế này:

public override Instruction GetRealInstruction(InstructionSet instructionSet, ExpressionElement argument) { 
    return new GoInstruction(instructionSet, argument); 
} 

Vì vậy, họ không phụ thuộc vào bất kỳ dữ liệu trong lớp; họ chỉ trả lại một cái gì đó mà phụ thuộc vào loại có nguồn gốc. (do đó thứ tự khởi tạo không ảnh hưởng đến chúng).

Vì vậy, tôi có nên bỏ qua nó không? Tôi không muốn; vì vậy bất cứ ai có thể chỉ cho tôi làm thế nào tôi có thể tránh cảnh báo này?

Tôi không thể sử dụng đại biểu gọn gàng vì phương pháp GetRealInstruction có thêm một tình trạng quá tải.

Trả lời

16

Tôi đã gặp sự cố này một vài lần và cách tốt nhất mà tôi đã tìm thấy để giải quyết đúng cách là trừu tượng phương thức ảo đang được gọi từ hàm tạo, vào một lớp riêng biệt. Sau đó bạn sẽ chuyển một thể hiện của lớp mới này vào hàm khởi tạo của lớp trừu tượng ban đầu của bạn, với mỗi lớp dẫn xuất truyền qua phiên bản riêng của nó cho hàm tạo cơ sở. Đó là một chút khó khăn để giải thích vì vậy tôi sẽ đưa ra một ví dụ, dựa trên của bạn.

public abstract class Instruction 
{ 
    protected Instruction(InstructionSet instructionSet, ExpressionElement argument, RealInstructionGetter realInstructionGetter) 
    { 
     if (realInstructionGetter != null) 
     { 
      RealInstruction = realInstructionGetter.GetRealInstruction(instructionSet, argument); 
     } 
    } 

    public Instruction RealInstruction { get; set; } 

    // Abstracted what used to be the virtual method, into it's own class that itself can be inherited from. 
    // When doing this I often make them inner/nested classes as they're not usually relevant to any other classes. 
    // There's nothing stopping you from making this a standalone class of it's own though. 
    protected abstract class RealInstructionGetter 
    { 
     public abstract Instruction GetRealInstruction(InstructionSet instructionSet, ExpressionElement argument); 
    } 
} 

// A sample derived Instruction class 
public class FooInstruction : Instruction 
{ 
    // Passes a concrete instance of a RealInstructorGetter class 
    public FooInstruction(InstructionSet instructionSet, ExpressionElement argument) 
     : base(instructionSet, argument, new FooInstructionGetter()) 
    { 
    } 

    // Inherits from the nested base class we created above. 
    private class FooInstructionGetter : RealInstructionGetter 
    { 
     public override Instruction GetRealInstruction(InstructionSet instructionSet, ExpressionElement argument) 
     { 
      // Returns a specific real instruction 
      return new FooRealInstuction(instructionSet, argument); 
     } 
    } 
} 

// Another sample derived Instruction classs showing how you effictively "override" the RealInstruction that is passed to the base class. 
public class BarInstruction : Instruction 
{ 
    public BarInstruction(InstructionSet instructionSet, ExpressionElement argument) 
     : base(instructionSet, argument, new BarInstructionGetter()) 
    { 
    } 

    private class BarInstructionGetter : RealInstructionGetter 
    { 
     public override Instruction GetRealInstruction(InstructionSet instructionSet, ExpressionElement argument) 
     { 
      // We return a different real instruction this time. 
      return new BarRealInstuction(instructionSet, argument); 
     } 
    } 
} 

Trong ví dụ cụ thể của bạn nó không có được một chút bối rối và tôi bắt đầu chạy ra khỏi tên hợp lý, nhưng điều này là do thực tế bạn đã có một tổ của Hướng dẫn trong Hướng dẫn, tức là một Chỉ thị có RealInstruction (hoặc ít nhất là tùy chọn); nhưng như bạn có thể thấy nó vẫn có thể đạt được những gì bạn muốn và tránh bất kỳ cuộc gọi thành viên ảo nào từ một hàm tạo.

Trong trường hợp vẫn chưa rõ ràng, tôi cũng sẽ đưa ra một ví dụ dựa trên một ví dụ tôi đã sử dụng gần đây trong mã của riêng mình. Trong trường hợp này, tôi có 2 loại biểu mẫu, một biểu mẫu tiêu đề và một biểu mẫu thư, cả hai đều được kế thừa từ một biểu mẫu cơ sở. Tất cả các biểu mẫu đều có các trường nhưng mỗi kiểu biểu mẫu có một cơ chế khác nhau để xây dựng các trường của nó, vì vậy ban đầu tôi có một phương thức trừu tượng được gọi là GetOrderedFields mà tôi gọi từ hàm tạo cơ sở và phương thức được ghi đè trong mỗi lớp biểu mẫu có nguồn gốc. Điều này đã cho tôi cảnh báo resharper bạn đề cập đến. Giải pháp của tôi là khuôn mẫu tương tự như trên và thực hiện như sau

internal abstract class FormInfo 
{ 
    private readonly TmwFormFieldInfo[] _orderedFields; 

    protected FormInfo(OrderedFieldReader fieldReader) 
    { 
     _orderedFields = fieldReader.GetOrderedFields(formType); 
    } 

    protected abstract class OrderedFieldReader 
    { 
     public abstract TmwFormFieldInfo[] GetOrderedFields(Type formType); 
    } 
} 

internal sealed class HeaderFormInfo : FormInfo 
{ 
    public HeaderFormInfo() 
     : base(new OrderedHeaderFieldReader()) 
    { 
    } 

    private sealed class OrderedHeaderFieldReader : OrderedFieldReader 
    { 
     public override TmwFormFieldInfo[] GetOrderedFields(Type formType) 
     { 
      // Return the header fields 
     } 
    } 
} 

internal class MessageFormInfo : FormInfo 
{ 
    public MessageFormInfo() 
     : base(new OrderedMessageFieldReader()) 
    { 
    } 

    private sealed class OrderedMessageFieldReader : OrderedFieldReader 
    { 
     public override TmwFormFieldInfo[] GetOrderedFields(Type formType) 
     { 
      // Return the message fields 
     } 
    } 
} 
+0

Tôi quên đề cập đến, nhưng lợi ích khác của cách này là trong trường hợp của bạn, bạn không cần phải có phương pháp cơ sở ảo, thay vì trừu tượng, do đó bạn có được kiểm tra biên dịch thay vì ném một ngoại lệ (như @TarasDzyoba đã đề cập) . – Richard

+0

Đó là suy nghĩ rất tốt, cảm ơn bạn. Cuối cùng tôi đã tránh được vấn đề này theo cách khác, nhưng đây là một giải pháp rất tốt trong tương lai. –

1

Bạn có thể vượt qua trong hướng dẫn thực vào constructor lớp cơ sở:

protected Instruction(..., Instruction realInstruction) 
{ 
    //Some stuff 

    if (DoesUseRealInstruction) { 
     RealInstruction = realInstruction; 
    } 
} 

public DerivedInstruction(...) 
    : base(..., GetRealInstruction(...)) 
{ 
} 

Hoặc, nếu bạn thực sự muốn gọi một hàm ảo từ constructor của bạn (mà tôi đánh giá cao discorage bạn từ), bạn có thể ngăn chặn các cảnh báo ReSharper:

// ReSharper disable DoNotCallOverridableMethodsInConstructor 
    RealInstruction = GetRealInstruction(instructionSet, Argument); 
// ReSharper restore DoNotCallOverridableMethodsInConstructor 
+0

Điều đó sẽ hoạt động tốt, nhưng nó sẽ không xóa lý do cảnh báo, chỉ cần ẩn nó. Tôi có thể sử dụng nó nếu không có giải pháp khác. Tôi thích làm việc một lần và thiết lập mọi thứ để thiết lập bản thân. Tôi không thích đưa ra thiết lập cho người gọi. –

+0

Trả lời sửa đổi. Nhưng tôi đoán nó vẫn không phải những gì bạn đang tìm kiếm. Và tôi nghi ngờ có một giải pháp như vậy. Có [lý do chính đáng] (http://stackoverflow.com/questions/119506/virtual-member-call-in-a-constructor?rq=1) cho cảnh báo ReSharper. –

+0

Tôi hiểu. Tôi đã kết thúc chuyển cuộc gọi 'GetRealInstruction' sang một hàm khác, để nó được gọi ngay khi cần. –

2

Khi bạn tạo một thể hiện của lớp được thừa kế của bạn, gọi stack của bạn sẽ trông như thế này:

GetRealInstruction() 
BaseContructor() 
DerivedConstructor() 

GetRealInstruction được ghi đè trong lớp dẫn xuất, mà hàm tạo chưa chạy xong.

Tôi không biết mã khác của bạn trông như thế nào, nhưng trước tiên bạn nên kiểm tra xem bạn có thực sự cần biến thành viên trong trường hợp này hay không. Bạn có một phương thức trả về đối tượng bạn cần. Nếu bạn thực sự cần nó, hãy tạo một tài sản và gọi GetRealInstruction() trong getter.

Ngoài ra, bạn có thể tạo GetRealInstruction trừu tượng. Bằng cách đó bạn không phải ném ngoại lệ và trình biên dịch sẽ cung cấp cho bạn một lỗi nếu bạn quên ghi đè nó trong một lớp dẫn xuất.

+0

Tôi hiểu vấn đề đặt hàng qua điện thoại. Tôi chỉ cần GetRealInstruction trong một số lớp học có nguồn gốc (những người có bộ bit doRequireRealInstruction) nên tôi không nên làm cho nó trừu tượng. Tôi không thực sự muốn làm cho một tài sản cho điều này bởi vì tôi thích sự khác biệt truy cập/hoạt động giữa các lĩnh vực và phương pháp. (Tôi sử dụng tài sản trong các trường hợp như thế này, nhưng đối với các hoạt động đơn giản hơn nhiều) tôi đã kết thúc cuộc gọi di chuyển ở một nơi khác. –

2

Bạn có thể giới thiệu một lớp trừu tượng RealInstructionBase do đó, mã của bạn sẽ trông giống như:

public abstract class Instruction { 
    public Instruction() { 
     // do common stuff 
    } 
} 

public abstract class RealInstructionBase : Instruction { 
    public RealInstructionBase() : base() { 
     GetRealInstruction(); 
    } 

    protected abstract object GetRealInstruction(); 
} 

Bây giờ mỗi hướng dẫn mà cần phải sử dụng RealInstruction xuất phát từ RealInstructionBase và tất cả những người khác bắt nguồn từ Chỉ thị. Bằng cách này, bạn nên có tất cả chúng được khởi tạo một cách chính xác.

EDIT: Ok, điều này sẽ chỉ cung cấp cho bạn thiết kế rõ ràng hơn (không nếu trong hàm khởi tạo) nhưng không loại bỏ cảnh báo. Bây giờ nếu bạn muốn biết tại sao bạn nhận được cảnh báo ngay từ đầu, bạn có thể tham khảo this question. Về cơ bản điểm là bạn sẽ được an toàn khi bạn đánh dấu các lớp học của bạn, nơi bạn đang thực hiện phương pháp trừu tượng như niêm phong.

+0

Điều đó được thừa nhận là sạch hơn. Tuy nhiên, tôi đã phân loại nó bằng cách di chuyển cuộc gọi 'GetRealInstruction' ngay trước khi nó là cần thiết, không phải trong hàm tạo. Chức năng này không quá đắt. –

0

lựa chọn khác là để giới thiệu một phương pháp Initialize(), nơi bạn làm tất cả những khởi tạo mà đòi hỏi một đối tượng hoàn toàn xây dựng.

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