Bruno Zell
Bruno Zell

Reputation: 8541

Why can't the type conversion of `this` be implicitly inferred from the generic contraint?

I have the following class:

public class Item<TItem>
    where TItem : Item<TItem>
{
    void GetReference()
    {
        TItem item = this;
    }
}

Here TItem item = this; generates a compiler error "can't convert Item<TItem> implicitly to TItem".

But why do we need a conversion here? We have defined the constraint where TItem : Item<TItem>, so one can think that no conversion is needed at all since the two types are the same, aren't they?

Btw an explicit conversion is available. This also is stated in the compiler error.

Upvotes: 3

Views: 214

Answers (3)

M.kazem Akhgary
M.kazem Akhgary

Reputation: 19149

because every TItem is Item<TItem> (as declared by where constraint), but not vice versa.

TItem could be more derived than Item<TItem>, so do this, TItem could be Apple and this could be oranges. so assignment is prevented by compiler.

currently in c# there is no way to declare that type parameter must match type of inheriting class itself.

there are two common ways to fix this. first use explicit cast

TItem GetReference() => (TItem) this;

Its your job to make sure inheriting class is using right type parameter, other wise you may get runtime exception if you ever try to use this method.

second way is to use return type of class itself. which is safe (no runtime exception) but does not have any sort of contract for derived classes. i.e you should make sure you write this method for every derived class.

Item<TItem> GetReference() => this;

now you can hide this method in derived classes.

new Derived GetReference() => this; // public class Derived : Item<Derived>

Note that this feature is already requested in GitHub c# repo, https://github.com/dotnet/csharplang/issues/252

you just have to wait for c# team to add this feature :)

Upvotes: 2

DiskJunky
DiskJunky

Reputation: 4971

That's because your class is type Item<TItem> not TItem. You could have;

Item<TItem> item = this;

The sample is a bit of a mind bender. To put this into context TItem item = this; resolves to trying to do

Item<TItem> item = new Item<Item<TItem>>();

Upvotes: 3

Jon Skeet
Jon Skeet

Reputation: 1500385

Because it wouldn't be safe. Consider:

public class GoodItem : Item<GoodItem>
{
    // No problem
}

public class EvilItem : Item<GoodItem>
{
    // GetReference body would be equivalent to 
    // GoodItem item = this;
    // ... but this *isn't* a GoodItem, it's an EvilItem!
}

EvilItem satisfies the constraint for TItem with no problems - GoodItem is indeed derived from Item<GoodItem>.

There's no way of expressing a relationship between the class being declared and a type parameter, which is what you really want.

Upvotes: 5

Related Questions