-2

Suppose I have

class Program
{
    static void Main(string[] args)
    {
        Apple myApple = new Apple(5);
        Orange myOrange = new Orange(5);
        bool isSame = myApple.Compare(myOrange);
    }
}

class Fruit
{
    private readonly int _Size;
    public Fruit(int size)
    {
        _Size = size;
    }
    public bool Compare<T>(T otherFruit) where T : Fruit
    {
        if (this._Size == otherFruit._Size) return true;
        return false;
    }
}

class Apple : Fruit
{
    public Apple(int size) : base(size) { }
}

class Orange : Fruit
{
    public Orange(int size) : base(size) { }
}

And I don't want to compare Apples to Oranges. I know I can

public bool Compare<T>(T otherFruit) where T : Fruit
{
    if (this.GetType() != otherFruit.GetType()) return false;
    if (this._Size == otherFruit._Size) return true;
    return false;
}

But it feels like I should be able to do it with type constraints.

I can do this (even though it's ugly) and it looks like it would work, but doesn't do what I expect

public bool Compare<T>(T otherFruit) where T : Fruit
{
    return Compare(otherFruit, this);
}

public static bool Compare<T>(T fruit1, T fruit2) where T : Fruit
{
    if (fruit1._Size == fruit2._Size) return true;
    return false;
}

How can I make it so my Compare method, defined in the base class, when run in child classes only allows a parameter type matching the child class? (So ideally at compile time you can only compare Apples to Apples and Oranges to Oranges)?

Interestingly, if I replace the instance compare method with an extension method, it does what I want / expect (I get compile time checks that I am only comparing Apples to Apples)

static class FruitComparer
{
    public static bool Compare<T>(this T currentFruit, T otherFruit) where T : Fruit
    {
        return Fruit.Compare(currentFruit, otherFruit);
    }
}

but again, it seems like there should be an easier way to do it... maybe I'm missing something super obvious?

EDIT: this actually works (you get compile time checks that only Apples can be compared to Apples), but only if called directly

public static bool Compare<T, U>(T fruit1, U fruit2) where T:Fruit,U where U:Fruit
{
    if (fruit1._Size == fruit2._Size) return true;
    return false;
}

Strangely, if you call that static method from inside my instance compare method, you do NOT get compile time checks - it lets you compare Apples to Oranges.

EDIT2: Here's how this would be implemented according the the linked question / Peter's comments

class Fruit<X>
{
    private readonly int _Size;
    public Fruit(int size)
    {
        _Size = size;
    }
    public bool Compare<T>(Fruit<T> otherFruit) where T: Fruit<X>
    {
        return true;
    }
}

class Apple : Fruit<Apple>
{
    public Apple(int size) : base(size) { }
}

class Orange : Fruit<Orange>
{
    public Orange(int size) : base(size) { }
}

And it does seem to work, but it feels like a lot of extra stuff just to get type constraints, and the apparent self reference feels weird to me. Maybe that's as good as it gets. I'm going to see if I can find more information about this pattern.

Aerik
  • 2,307
  • 1
  • 27
  • 39
  • You should make the base class generic, where the type parameter is the type of the derived type. E.g. `class Apple : Fruit`. Then code won't be allowed to use the base class without being specific about _which_ derived type it is expecting, ensuring you only compare apples to apples, oranges to oranges, and not apples to oranges. See duplicate. – Peter Duniho Jul 21 '21 at 18:35
  • Whoever closed the question - while that question does seem to be asking the same thing in the title, it's actually somewhat different and arguably more complex. Certainly the answers given have even more complexity and the extension method possibility I put forward here. – Aerik Jul 21 '21 at 18:48
  • The context of the question is slightly different, but it's essentially the same, with the same answer. If you cannot constrain callers to knowing what fruit they're dealing with when they are dealing with the base class, then there's no way to guarantee compatible comparisons. If you _can_ constrain callers, then doing so by moving the generic type parameter from the method to the class does exactly what you want, just like in the duplicate. – Peter Duniho Jul 21 '21 at 18:51
  • @PeterDuniho That pattern - the generic base class thing - is interesting. However I don't find any references that either 1) prove that answer is the most correct (ie, "how do I correctly...") or that support your supposition "You *should*". Certainly it does look like I could make that do what I'm after, but it seems just as (unnecessarily) complex as other options I explored. Also, the fact that the static compare method can do what I'm after seems to hint that something is missing in the instance method. – Aerik Jul 21 '21 at 18:56
  • If you asked the question with the intent to find "the best" solution, then you should not have posted the question, as such questions are primarily opinion-based and unsuitable for Stack Overflow. If you already know of solutions that will work, you should post them to the duplicate and wait to see which gets upvoted the most to find out what the community thinks is "the best". – Peter Duniho Jul 21 '21 at 18:58
  • _"the fact that the static compare method can do what I'm after"_ -- does it? You can use `Fruit` as the type parameter and it still has the same problem: `Apple apple; Orange orange; bool result = ((Fruit)apple).Compare(orange);` Or even `Fruit apple; Orange orange; bool result = apple.Compare(orange);` Like I said, the only reliable way to do this is to force the relationship between the base type and derived type, and require the caller to observe that relationship. – Peter Duniho Jul 21 '21 at 19:02
  • Ooh, that's an interesting find, thanks (that if you pass the base type to Compare the derived type gets cast as the base). The extension method has the same issue. – Aerik Jul 21 '21 at 19:06
  • The whole business of a generic base type bothers me. You can make a Fruit which is just silly. – Aerik Jul 21 '21 at 19:40
  • @PeterDuniho One more comment, the other question asks about a "related class type" (OP: "I have two base classes BaseObject and BaseObjectSettings") whereas this question is specific to a "child class type". These are, in fact, different questions, even if they are solved with similar solutions. I may re-phrase and re-ask my question in hopes of getting some other creative answers. – Aerik Jul 21 '21 at 19:48
  • _"You can make a Fruit"_ -- not if you declared `class Fruit where T : Fruit`. See e.g. https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern and https://stackoverflow.com/questions/3783321/why-does-this-generic-constraint-compile-when-it-seems-to-have-a-circular-refere – Peter Duniho Jul 21 '21 at 20:01
  • _"These are, in fact, different questions, even if they are solved with similar solutions"_ -- an important skill to develop as a programmer is the ability to abstract problems and recognize when one problem is really the same as another. It may seem to you that the duplicate is a different question, but it really is just asking the same thing: how to constrain the generic context to some specific derived type. – Peter Duniho Jul 21 '21 at 20:03
  • 1
    It's called the [Curiously Recurring Template Pattern](https://ericlippert.com/2011/02/02/curiouser-and-curiouser/), and it's currently the only way to enforce this in C#, because there is no `where T : this` constraint – Charlieface Jul 21 '21 at 21:39
  • 1) Asking "how to make a cake rise" is not the same as "how to make sourdough bread rise", and abstracting a problem out to what seems to be a common model is not always appropriate. 2) sometimes a high level of abstraction results in an unnecessarily complex "solution". 3) A condescending attitude has no place on StackOverflow. (Also, not sure who keeps deleting this comment, but I'm going to keep posting it until I get an explanation) – Aerik Aug 17 '21 at 16:41

1 Answers1

0

There are two arguments and two type parameters involved. You need to make sure they are both fruit and that they are the same type.

public bool CompareFruit<T1, T2>(T1 fruit1, T2 fruit2) where T1 : Fruit, T2
                                                       where T2 : Fruit
{
    return fruit1.Size == fruit2.Size;
}
John Wu
  • 50,556
  • 8
  • 44
  • 80
  • 1
    This doesn't seem very useful or safe to me. What happens when there's an intermediate class `StoneFruit : Fruit` and the caller calls `CompareFruit(fruit1, fruit2)` but it turns out that `fruit2` is actually an `Apricot`? You're still not guaranteeing that the comparison is between identical types. – Peter Duniho Jul 21 '21 at 18:38
  • I actually did try public static bool Compare(T fruit1, U fruit2) where T:Fruit,U where U:Fruit { if (fruit1._Size == fruit2._Size) return true; return false; } and that works when you call it by itself, but if you call it from within the instance compare, it lets you compare Apples to Oranges – Aerik Jul 21 '21 at 18:43