1

where (generic type constraint) (C# Reference)

The relevant text from the document cited above is:

For example, you can declare a generic class, MyGenericClass, such that the type parameter T implements the IComparable<T> interface:

Here is MyGenericClass, copied verbatim from documentation cited above:

public class AGenericClass<T> where T : IComparable<T> { }

Here is a small console app shows my attempts to create the instance:

class Program
{
    static void Main(string[] args)
    {
        AGenericClass<MyComparable<string>> stringComparer = new AGenericClass<MyComparable<string>>(); // does not build


        AGenericClass<MyStringComparable> stringComparer2 = new AGenericClass<MyStringComparable>(); // does not build
    }
}


public class MyStringComparable : IComparable<string>
{
    public int CompareTo(string other) => throw new NotImplementedException();
}

public class MyComparable<T> : IComparable<T>
{
    public int CompareTo(T other) => throw new NotImplementedException();
}

// verbatim from documentation cited above
public class AGenericClass<T> where T : IComparable<T> { }  

For clarity my question is "How do I create an instance of AGenericClass as it is defined in the cited C# documentation". Please note my question relates to the specific example cited above, not other related questions such as this one and this one.

I'm obviously very confused on how type parameters work. I hope by answering this question I will become enlightened as it closely resembles the business problem I'm trying to solve.

Also my question has nothing to do with IComparable<T> or comparing objects - that just happens to be the interface in the example code.

Edit: Additional code provided based on reply from @CoolBots. This code provides a more realistic example and shows an interface that is intended to operate on a single object:

public class Program2
{

    public Program2()
    {
        Selector<Selectable<string>, string> stringSelector = new Selector<Selectable<string>, string>();
    }
}

public interface ISelectable<T>
{
    bool IsSelected { get; set; }
    T Item { get; set; }
}

public class Selectable<T> : ISelectable<T>
{
    public bool IsSelected { get; set; }
    public T Item { get; set; }
}

public class Selector<T, TData> where T:ISelectable<TData>
{
}
  • You actually need 2 type parameters to accomplish what you're trying to do - since `MyComparable` is a container, and is not a comparable of itself (but only of the type `T` it contains). – CoolBots Sep 22 '19 at 21:35
  • I still don't get it. Would you mind writing up a brief example, bearing in mind the objective of instantiating `AGenericClass`. –  Sep 22 '19 at 21:42
  • I updated my answer based on your edit and our comments thread on my answer. – CoolBots Sep 22 '19 at 23:48
  • It would be helpful if you would review your post, as your text implies that you mean to provide links to two different Stack Overflow questions, but the first such link is just a reference to the C# documentation (the same link you used at the top of your post). Also, you copied code from one answer into your question, but did not explain why that was supposed to somehow improve the question. What was your intent there? Please don't include code from answers, unless doing so is somehow a way to elaborate and clarify the question you actually have. – Peter Duniho Sep 23 '19 at 00:56

2 Answers2

1

In your specific example, the generic parameter T in AGenericClass<T>, and the corresponding constraint, where T: IComparable<T> specify that the generic parameter T must implement IComparable<T> interface (which is not important to your question, but important to understanding what's going on, since this interface itself is generic). Note that T in IComparable<T> is the same T as in AGenericClass<T> - that is, you're restricting to a type that implements IComparable<T> of itself - a string is a great example, because it is IComparable<string> However, your MyStringComparable is not an IComparable<MyStringComparable> - it is an IComparable<string>, which fails the generic type constraint on AGenericClass<T>, as defined.

The solution here is to use 2 generic parameters - one for container, and one for the type contained:

class AGenericClass<TContainer, TData> where TContainer: IComparable<TData> { }

Instantiation is as follows:

var stringComparer = new AGenericClass<MyComparable<string>, string>();

var stringComparer2 = new AGenericClass<MyStringComparable, string>();

EDIT, based on your updated question and comments:

If you really want to get rid of the second generic parameter, something's gotta give - for instance, we can lose the ability to specify a container, making it a pass-through, similar to System.Collections.Generic.LinkedList<T>'s implementation - LinkedListNode<T> is a pass-through container, which is known to the LinkedList<T> class, and cannot be swapped out for a different kind of container:

public class Selector<T> //no constraint at all
{
   private Selectable<T> container; //the container is always Selectable<T>

   public Selector(T item)
      => container = new Selectable<T>() { Item = item };
}

Usage is simpler:

var selector = new Selector<string>("some data"); // no need to pass Selectable<string> here

However, you are now stuck with the Selectable<T> as the container - you can't make a BetterSelectable<T> and use it as the container interchangeably. Basically, it comes down to your use case - do you need 2 variables - a "container" and a "data contained", or are you ok with only one - just the data contained. In the former case, you must have 2 generic parameters (because you have 2 variables); in the latter case, you only need 1 generic parameter (because you have 1 variable, and one constant, namely the container).

CoolBots
  • 4,770
  • 2
  • 16
  • 30
  • So (I think) you are saying the second type parameter describes the type of the type of the first parameter. This makes sense and is a good solution. However, I am interested to know how Microsoft thinks this can be done using one parameter as shown in their code. At this point this a purely academic question. –  Sep 22 '19 at 22:27
  • @Sam the second type parameter, `TData` describes the *containing type* within the container described by the first type parameter, `TContainer`. Note, they **must** match - `new AGenericClass()` won't build. Microsoft's example works if you don't have an additional wrapper/container - for instance, your original code will work with `new AGenericClass()`, as the constraint fits. The issue here is specifically the introduction of the container, which is in itself a type, and is subject to constraint check when used as a generic parameter. – CoolBots Sep 22 '19 at 22:40
  • I've updated my question to include code that matches my business problem. Here the redundancy is more apparent because we are dealing with an instance of T and wrapping it with a class with a bool property. Is it possible to create this with a single Type parameter? Re: Note, they must match -------- so why do we need two parameters? How can we achieve this with one parameter? –  Sep 22 '19 at 22:57
  • @Sam in your use case, I don't think you can factor out the second generic parameter; in order to eliminate it, but keep the wrapper (`Selectable`), you'd need a non-generic interface on the container; say, `ISelectable` in your case (or a non-generic base class). Then `T` on `Selector` can be constrained to `ISelectable`, factoring out the generic type contained in the `ISelectable` that's passed in. The problem with this approach is accessing `T Item` in the `ISelectable` - there's no way to move it to that interface, since it's a generic property (you can move `bool IsSelected` only)... – CoolBots Sep 22 '19 at 23:22
  • @Sam...if that works in your use case - say, you have methods to access the generic data that can themselves be described by a non-generic interface or base class, then you can eliminate the second generic parameter; if, however, you have to be able to access `T Item` in the `Selector` class, and you have to be able to specify the container type (`Selectable`), there's no way of avoiding the second generic parameter. – CoolBots Sep 22 '19 at 23:26
  • OK...I think I have my answer. Would you please write in the top of your answer something to the effect "... the code provided in the c# documentation is wrong. A class matching the signature of the class in the example cannot be instantiated...". I will accept your answer. Thank you. –  Sep 23 '19 at 13:39
  • @Sam: it is**not true** that _"the code provided in the C# documentation is wrong"_. You certainly can create a class matching the signature of the class in the example. See my answer for details. – Peter Duniho Sep 23 '19 at 15:05
  • @Sam the code in C# documentation is correct - my edit yesterday shows instalation of a class with a single generic parameter; also, @Peter Duniho explained in great detail how to instantiate your version of `AGenericClass`. While I'd appreciate you accepting my answer, I am not here for the points; I certainly cannot say that C# documentation is wrong when it's very clearly correct. – CoolBots Sep 23 '19 at 15:21
  • OK I finally got it. I just had to write it and write it till I understood. Thanks for your patience @PeterDuniho and CoolBots. –  Sep 23 '19 at 19:39
  • @Sam: no problem...glad we were able to help! – Peter Duniho Sep 23 '19 at 20:47
1

For clarity my question is "How do I create an instance of AGenericClass as it is defined in the cited C# documentation"

By providing a type for the type parameter T that fulfills the constraint T : IComparable<T>. In your code example:

static void Main(string[] args)
{
    AGenericClass<MyComparable<string>> stringComparer = new AGenericClass<MyComparable<string>>(); // does not build


    AGenericClass<MyStringComparable> stringComparer2 = new AGenericClass<MyStringComparable>(); // does not build
}

You get compile-time errors because neither of the types you've provided satisfy that constraint. The way to fix the errors is to provide a type that does satisfy the constraint.

There are lots of types that already do satisfy the constraint. For example, int or any other primitive numeric type would. But presumably your question really is meant to be worded something more like this:

For clarity my question is "How do I create an instance of AGenericClass as it is defined in the cited C# documentation, using a user-defined type of my own as the type parameter"

The answer is still the same: satisfy the constraint in your user-defined type. So, why don't the types you already have do that? Let's see...

Here are your types:

public class MyStringComparable : IComparable<string>
{
    public int CompareTo(string other) => throw new NotImplementedException();
}

public class MyComparable<T> : IComparable<T>
{
    public int CompareTo(T other) => throw new NotImplementedException();
}

Let's test them one at a time. Let's suppose we set T to MyStringComparable. To satisfy the constraint, that type would need to implement IComparable<T> where T is the type you've provided as the type parameter. Since you set T to MyStringComparable, then MyStringComparable needs to implement IComparable<MyStringComparable>.

Does it? No, it does not. It implements IComparable<T> for some other type parameter T, namely string.

You would need something like this, in order to satisfy the constraint:

public class MyStringComparable : IComparable<MyStringComparable>
{
    public int CompareTo(MyStringComparable other) => throw new NotImplementedException();
}

Okay, what about the second one. Do the same test, using MyComparable<T> as the type parameter. Of course, you need to provide a type parameter for that class, since it's generic. In your example, you use string as the type parameter, so it's MyComparable<string>.

According to the constraint, the type parameter MyComparable<string> needs to implement IComparable<MyComparable<string>>. Does it? No, it does not. The MyComparable<string> class only implements IComparable<string>. Again, this is the wrong constraint.

A class that would implement the correct constraint would look more like this:

public class MyComparable<T> : IComparable<MyComparable<T>>
{
    public int CompareTo(MyComparable<T> other) => throw new NotImplementedException();
}

Generic type constraints can be tricky to deal with. Another area that people often get tangled up in is where trying to inherit or otherwise use an existing type parameter that has a constraint, forgetting that they need to satisfy the constraint in that context. Often this is done by repeating the constraint in their own generic class or method.

I'll also note with respect to this text in your question:

Also my question has nothing to do with IComparable or comparing objects - that just happens to be the interface in the example code.

Actually, your question has at least a moderate something to do with IComparable<T>, because it's often used in this sort of recursive way that trips people up. You wouldn't have run into the same problem with a more conventional scenario.

The scenario you've run into is the C# variation of C++'s "Curiously Recurring Template Pattern". Eric Lippert's written a useful article on the topic, which you might like to read as you explore and learn about generic type parameters and their constraints.

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
  • Thanks for your comments Peter. > The way to fix the errors is to provide a type that does satisfy the constraint. OK, how do we do that? Can you provide example code that compiles and runs and shows `AGenericClass` actually being instantiated? –  Sep 23 '19 at 12:58
  • > You would need something like this, in order to satisfy the constraint:......... I tried the provided code and it does not build. Please don't be constrained with `MyStringComparable` or `MyComparable`. Those are just my attempts to answer the question. > But presumably your question really is meant to be worded something more like this.... No, the question as worded originally does not exclude this so if you know how to do it please share. –  Sep 23 '19 at 12:58
  • @Sam: _"Can you provide example code that compiles and runs and shows AGenericClass actually being instantiated?"_ -- as long as you define your generic classes with the constraints correctly (as I describe above), it is trivial to instantaite `AGenericClass` using either of your other user-defined types as type parameters. E.g. `new AGenericClass` or `new AGenericClass>`, just like you've already tried and failed. The thing that's wrong isn't the code that tries to instantiate `AGenericClass`, but rather the declarations of the other types. – Peter Duniho Sep 23 '19 at 15:08