Lựa chọn phương pháp tổng quát C #


9

Tôi đang cố gắng viết các thuật toán chung trong C # có thể hoạt động với các thực thể hình học có kích thước khác nhau.

Trong ví dụ giả định sau đây tôi có Point2Point3, cả hai đều thực hiện một IPointgiao diện đơn giản .

Bây giờ tôi có một chức năng GenericAlgorithmgọi một chức năng GetDim. Có nhiều định nghĩa về chức năng này dựa trên loại. Ngoài ra còn có một chức năng dự phòng được xác định cho bất cứ điều gì thực hiện IPoint.

Ban đầu tôi dự kiến ​​đầu ra của chương trình sau là 2, 3. Tuy nhiên, nó là 0, 0.

interface IPoint {
    public int NumDims { get; } 
}

public struct Point2 : IPoint {
    public int NumDims => 2;
}

public struct Point3 : IPoint {
    public int NumDims => 3;
}

class Program
{
    static int GetDim<T>(T point) where T: IPoint => 0;
    static int GetDim(Point2 point) => point.NumDims;
    static int GetDim(Point3 point) => point.NumDims;

    static int GenericAlgorithm<T>(T point) where T : IPoint => GetDim(point);

    static void Main(string[] args)
    {
        Point2 p2;
        Point3 p3;
        int d1 = GenericAlgorithm(p2);
        int d2 = GenericAlgorithm(p3);
        Console.WriteLine("{0:d}", d1);        // returns 0 !!
        Console.WriteLine("{0:d}", d2);        // returns 0 !!
    }
}

OK, vì một số lý do, thông tin loại bê tông bị mất GenericAlgorithm. Tôi không hiểu tại sao điều này xảy ra, nhưng tốt thôi. Nếu tôi không thể làm theo cách này, tôi có những lựa chọn thay thế nào khác?


2
"Ngoài ra còn có chức năng dự phòng" Mục đích của việc này là gì? Toàn bộ quan điểm của việc thực hiện một giao diện là đảm bảo rằng NumDimstài sản có sẵn. Tại sao bạn lại bỏ qua nó trong một số trường hợp?
John Wu

Vì vậy, nó biên dịch, về cơ bản. Ban đầu, tôi nghĩ rằng chức năng dự phòng trở lại là bắt buộc nếu tại thời điểm chạy trình biên dịch JIT không thể tìm thấy một triển khai chuyên biệt cho GetDim(tức là tôi vượt qua Point4nhưng GetDim<Point4>không tồn tại). Tuy nhiên, dường như cả hai trình biên dịch đều không tìm kiếm một triển khai chuyên biệt.
mohamedmoussa

1
@woggy: Bạn nói rằng "dường như cả hai trình biên dịch đều không tìm kiếm một triển khai chuyên biệt" như thể đây là vấn đề của sự lười biếng đối với các nhà thiết kế và người thực hiện. Nó không thể. Đó là vấn đề làm thế nào thuốc generic được thể hiện trong .NET. Nó không phải là loại chuyên môn giống như tạo khuôn mẫu trong C ++. Một phương thức chung không được biên dịch riêng cho từng đối số loại - nó được biên dịch một lần. Chắc chắn có những ưu và nhược điểm của việc này, nhưng đó không phải là vấn đề "làm phiền".
Jon Skeet

@jonskeet Xin lỗi nếu lựa chọn ngôn ngữ của tôi kém, tôi chắc chắn có những điều phức tạp ở đây mà tôi chưa xem xét. Sự hiểu biết của tôi là trình biên dịch không biên dịch các hàm riêng biệt cho các kiểu tham chiếu, nhưng nó cho các kiểu / cấu trúc giá trị, điều đó có đúng không?
mohamedmoussa

@woggy: Đó là trình biên dịch JIT , một vấn đề hoàn toàn tách biệt với trình biên dịch C # - và đó là trình biên dịch C # thực hiện độ phân giải quá tải. IL cho phương thức chung chỉ được tạo một lần - không phải một lần cho mỗi chuyên môn.
Jon Skeet

Câu trả lời:


10

Phương pháp này:

static int GenericAlgorithm<T>(T point) where T : IPoint => GetDim(point);

... sẽ luôn gọi GetDim<T>(T point). Độ phân giải quá tải được thực hiện tại thời gian biên dịch và ở giai đoạn đó không có phương pháp áp dụng nào khác.

Nếu bạn muốn độ phân giải quá tải được gọi trong thời gian thực hiện , bạn cần sử dụng kiểu gõ động, vd

static int GenericAlgorithm<T>(T point) where T : IPoint => GetDim((dynamic) point);

Nhưng nói chung, đó là một ý tưởng tốt hơn để sử dụng tính kế thừa cho điều này - trong ví dụ của bạn, rõ ràng bạn chỉ có thể có phương thức duy nhất và trả về point.NumDims. Tôi giả sử trong mã thực của bạn có một số lý do tương đương khó thực hiện hơn, nhưng không có nhiều ngữ cảnh hơn, chúng tôi không thể tư vấn về cách sử dụng tính kế thừa để thực hiện chuyên môn hóa. Đó là những lựa chọn của bạn mặc dù:

  • Kế thừa (ưu tiên) để chuyên môn hóa dựa trên loại thời gian thực hiện của mục tiêu
  • Gõ động cho độ phân giải quá tải thời gian thực hiện

Tình hình thực tế là tôi có một AxisAlignedBoundingBox2AxisAlignedBoundingBox3. Tôi có một Containsphương thức tĩnh được sử dụng để xác định xem một tập hợp các hộp có chứa một Line2hoặc Line3(cái nào phụ thuộc vào loại hộp). Logic thuật toán giữa hai loại hoàn toàn giống nhau, ngoại trừ số lượng kích thước là khác nhau. Cũng có những cuộc gọi đến Intersectnội bộ cần phải được chuyên môn đúng loại. Tôi muốn tránh các cuộc gọi chức năng ảo / động, đó là lý do tại sao tôi đang sử dụng generic ... tất nhiên, tôi chỉ có thể sao chép / dán mã và tiếp tục.
mohamedmoussa

1
@woggy: Thật khó để hình dung điều đó chỉ từ một mô tả. Nếu bạn muốn giúp đỡ cố gắng thực hiện điều này bằng cách sử dụng tính kế thừa, tôi khuyên bạn nên tạo một câu hỏi mới với một ví dụ tối thiểu nhưng đầy đủ.
Jon Skeet

OK, sẽ làm, tôi sẽ chấp nhận câu trả lời này ngay bây giờ vì có vẻ như tôi chưa cung cấp một ví dụ hay.
mohamedmoussa

6

Kể từ C # 8.0, bạn sẽ có thể cung cấp cài đặt mặc định cho giao diện của mình, thay vì yêu cầu phương thức chung.

interface IPoint {
    int NumDims { get => 0; }
}

Việc thực hiện một phương pháp chung và quá tải cho mỗi lần IPointthực hiện cũng vi phạm Nguyên tắc thay thế Liskov (L trong RẮN). Bạn sẽ tốt hơn để đẩy thuật toán vào mỗi lần IPointthực hiện, điều đó có nghĩa là bạn chỉ cần một cuộc gọi phương thức duy nhất:

static int GetDim(IPoint point) => point.NumDims;

3

Mẫu khách

thay thế cho dynamicviệc sử dụng, bạn có thể muốn sử dụng mẫu Khách truy cập như dưới đây:

interface IPoint
{
    public int NumDims { get; }
    public int Accept(IVisitor visitor);
}

public struct Point2 : IPoint
{
    public int NumDims => 2;

    public int Accept(IVisitor visitor)
    {
        return visitor.Visit(this);
    }
}

public struct Point3 : IPoint
{
    public int NumDims => 3;

    public int Accept(IVisitor visitor)
    {
        return visitor.Visit(this);
    }
}

public class Visitor : IVisitor
{
    public int Visit(Point2 toVisit)
    {
        return toVisit.NumDims;
    }

    public int Visit(Point3 toVisit)
    {
        return toVisit.NumDims;
    }
}

public interface IVisitor<T>
{
    int Visit(T toVisit);
}

public interface IVisitor : IVisitor<Point2>, IVisitor<Point3> { }

class Program
{
    static int GetDim<T>(T point) where T : IPoint => 0;
    static int GetDim(Point2 point) => point.NumDims;
    static int GetDim(Point3 point) => point.NumDims;

    static int GenericAlgorithm<T>(T point) where T : IPoint => point.Accept(new Visitor());

    static void Main(string[] args)
    {
        Point2 p2;
        Point3 p3;
        int d1 = GenericAlgorithm(p2);
        int d2 = GenericAlgorithm(p3);
        Console.WriteLine("{0:d}", d1);        // returns 2
        Console.WriteLine("{0:d}", d2);        // returns 3
    }
}

1

Tại sao bạn không xác định hàm GetDim trong lớp và giao diện? Trên thực tế, bạn không cần xác định hàm GetDim, chỉ cần sử dụng thuộc tính NumDims.

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.