Max
Max

Reputation: 325

passing "this" in C# generic doesnt work without casting

I tried googling and stackoverflow but couldnt find answer to my specific problem (and even limited knoweldge of generics)

So I posted here in hope for the answer.

Here is my class

public abstract class AThemeableControl<TManager, TControl>
    where TManager:AManagerTheme<TManager, TControl>
    where TControl:AThemeableControl<TManager, TControl>
{
    public abstract void UpdateTheme(TManager managerTheme);
}

Here is the manager class

public abstract class AManagerTheme<TManager, TControl>
    where TManager:AManagerTheme<TManager, TControl>
    where TControl:AThemeableControl<TManager, TControl>
{
    public TControl[] ThemableControls;

    virtual public void ApplyTheme()
    {
        for (int i = ThemableControls.Length-1; i >= 0; i--)
        {
            ThemableControls[i].UpdateTheme(this); //ERROR HERE           
        }           
    }
}

So I'm able to solve this error by typecasting

ThemableControls[i].UpdateTheme((TManager) this);

But I want to know the solution without typecasting which I'm hopeful is possible.

Upvotes: 3

Views: 254

Answers (1)

Jon Skeet
Jon Skeet

Reputation: 1500375

You can't remove the cast, because it would be unsafe to do so. Here's an example where the cast throws:

using System;

public abstract class AManagerTheme<TManager, TControl>
    where TManager : AManagerTheme<TManager, TControl>
    where TControl : AThemeableControl<TManager, TControl>
{
    public TControl[] ThemableControls;

    public virtual void ApplyTheme()
    {
        for (int i = ThemableControls.Length - 1; i >= 0; i--)
        {
            ThemableControls[i].UpdateTheme((TManager) this);
        }           
    }
}

public abstract class AThemeableControl<TManager, TControl>
    where TManager : AManagerTheme<TManager, TControl>
    where TControl : AThemeableControl<TManager, TControl>
{
    // Empty implementation; irrelevant for the question.
    public void UpdateTheme(TManager managerTheme) {}
}

public class NormalTheme : AManagerTheme<NormalTheme, NormalControl>
{
}

public class NormalControl : AThemeableControl<NormalTheme, NormalControl>
{
}

public class EvilTheme : AManagerTheme<NormalTheme, NormalControl>
{
}

public class Test
{
    static void Main()
    {
        var theme = new EvilTheme
        {
            ThemableControls = new[] { new NormalControl() }
        };
        theme.ApplyTheme();
    }
}

This is what you'd like to prevent:

public class EvilTheme : AManagerTheme<NormalTheme, NormalControl>

... but you can't express that with C# generics. (I faced the same sort of thing in an earlier version of Protocol Buffers, where each "message" type had a corresponding "builder" type.) The C# type system just isn't rich enough to express this.

You might want to check for validity within the abstract class constructor, so that at least you know the cast will work later. But you can't avoid either casting or using a different TManager somewhere.

Upvotes: 8

Related Questions