C # jenerik yöntem seçimi


9

Farklı boyutta geometrik varlıkları ile çalışabilir C # genel algoritmaları yazmaya çalışıyorum.

Ben şu yapmacık örnekte Point2ve Point3hem basit uygulayan IPointbir arayüz.

Şimdi bir işlevi GenericAlgorithmçağıran bir fonksiyon var GetDim. Bu işlevin türe bağlı olarak birden fazla tanımı vardır. Ayrıca, uygulayan her şey için tanımlanmış bir geri dönüş işlevi de vardır IPoint.

Başlangıçta aşağıdaki programın çıktısının 2, 3 olmasını bekledim. Ancak, 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 !!
    }
}

Tamam, bu yüzden somut tipteki bilgiler kaybolur GenericAlgorithm. Bunun neden olduğunu tam olarak anlamıyorum, ama iyi. Bu şekilde yapamazsam, başka hangi alternatiflerim var?


2
“Bir geri dönüş fonksiyonu da var” Bunun amacı tam olarak nedir? Bir arayüzün uygulanmasının tüm amacı, NumDimsmülkün kullanılabilir olduğunu garanti etmektir. Neden bazı durumlarda görmezden geliyorsun?
John Wu

Yani temelde derler. Başlangıçta, çalışma zamanında JIT derleyicisi için özel bir uygulama bulamazsa geri düşme işlevinin gerekli olduğunu düşündüm GetDim(yani geçiyorum Point4ama GetDim<Point4>mevcut değil). Ancak, derleyici özel bir uygulama aramak için rahatsız edici görünmüyor.
mohamedmoussa

1
@woggy: "Derleyici özel bir uygulama aramak rahatsız edici görünmüyor" diyorsunuz, sanki bu tasarımcılar ve uygulayıcılar için tembellik meselesiymiş gibi. Değil. Jeneriklerin .NET'te nasıl temsil edildiği meselesidir. C ++ 'da templating yapmakla aynı uzmanlık türü değil. Genel bir yöntem her tür argümanı için ayrı olarak derlenmez - bir kez derlenir. Kesinlikle bunun artıları ve eksileri var, ama bu bir "rahatsız etme" meselesi değil.
Jon Skeet

@jonskeet Özür dilerim dil seçimim zayıfsa, burada dikkate almadığım karmaşıklıklar olduğuna eminim. Benim anlayış derleyici referans türleri için ayrı işlevleri derlemiyordu, ama değer türleri / yapıları için yapar, bu doğru mu?
mohamedmoussa

@woggy: C # derleyicisinden tamamen ayrı bir konu olan JIT- derleyicisi - ve aşırı yük çözünürlüğü gerçekleştiren C # derleyicisi. Jenerik yöntem için IL sadece bir kez üretilir - uzmanlık başına bir kez değil.
Jon Skeet

Yanıtlar:


10

Bu method:

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

... her zaman arayacak GetDim<T>(T point). Aşırı yük çözünürlüğü derleme zamanında gerçekleştirilir ve bu aşamada başka uygulanabilir bir yöntem yoktur.

Aşırı yük çözünürlüğünün yürütme sırasında çağrılmasını istiyorsanız, dinamik yazmayı kullanmanız gerekir, örn.

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

Ancak bunun için kalıtım kullanmak genellikle daha iyi bir fikirdir - örneğin, açıkçası tek bir yönteme ve geri dönüşe sahip olabilirsiniz point.NumDims. Gerçek kodunuzda, eşdeğerin daha zorlayıcı olmasının bir nedeni olduğunu varsayıyorum, ancak daha fazla bağlam olmadan, uzmanlığı gerçekleştirmek için mirasın nasıl kullanılacağına dair tavsiyede bulunamayız. Bunlar seçenekleriniz:

  • Hedefin yürütme zamanı türüne göre uzmanlaşma için kalıtım (tercih edilen)
  • Yürütme zamanı aşırı yük çözünürlüğü için dinamik yazma

Gerçek durum benim AxisAlignedBoundingBox2ve AxisAlignedBoundingBox3. Bir var Containsbir olmadığını belirlemek için kullanılan statik yöntemini toplama kutuları içeren Line2veya Line3(bir kutu türüne bağlıdır ki). Boyut sayısı farklı olmadıkça, iki tür arasındaki algoritma mantığı tamamen aynıdır. Ayrıca Intersectdahili olarak doğru tipte uzmanlaştırılması gereken çağrılar da vardır . Sanal fonksiyon çağrılarından / dinamiklerden kaçınmak istiyorum, bu yüzden jenerikler kullanıyorum ... tabii ki, sadece kodu kopyalayıp yapıştırabilir ve devam edebilirim.
mohamedmoussa

1
@woggy: Bunu sadece bir açıklama ile görselleştirmek oldukça zor. Kalıtım kullanarak bunu yapmaya çalışırken yardım istiyorsanız, minimal ama eksiksiz bir örnekle yeni bir soru oluşturmanızı öneririm.
Jon Skeet

Tamam, yapacağım, iyi bir örnek vermediğim için şimdilik bu cevabı kabul edeceğim.
mohamedmoussa

6

C # 8.0'dan itibaren, genel yöntem gerektirmek yerine, arabiriminiz için varsayılan bir uygulama sağlayabilmeniz gerekir.

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

Genel bir yöntem IPointuygulamak ve uygulama başına aşırı yüklenmeler de Liskov İkame İlkesini (SOLID'deki L) ihlal eder. Algoritmayı her IPointuygulamaya uygulamak daha iyi olur , yani yalnızca tek bir yöntem çağrısına ihtiyacınız vardır:

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

3

Ziyaretçi Deseni

dynamickullanıma alternatif olarak, aşağıdaki gibi bir Ziyaretçi kalıbı kullanmak isteyebilirsiniz :

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

Neden GetDim işlevini sınıfta ve arabirimde tanımlamıyorsunuz? Aslında GetDim işlevini tanımlamanız gerekmez, sadece NumDims özelliğini kullanın.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.