Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c#

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.

like image 224
Max Avatar asked Apr 17 '26 02:04

Max


1 Answers

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.

like image 117
Jon Skeet Avatar answered Apr 18 '26 15:04

Jon Skeet



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!