Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# Create Instance of Generic class which inheritance from base

Tags:

c#

.net

I'm trying to create instance of class Bar but I'm receiving an error:

"Cannot implicitly convert type ConsoleApplication1.Bar to ConsoleApplication1.BaseFoo<ConsoleApplication1.baseOutput, ConsoleApplication1.baseInput>"

Any idea what I'm missing or what I'm doing wrong? Any advice will be nice.

public class baseOutput
{
    public string output;
}

public class baseInput
{
    public string input;
}

public class ExtendOutput : baseOutput
{
    public long id;
}

public class ExtendInput : baseInput
{
    public long id;
}

public class BaseFoo<baseOutput, baseInput>
{
    protected virtual void DoSmth()
    {

    }
}

public class Bar : BaseFoo<ExtendOutput, ExtendInput>
{
    protected override void DoSmth()
    {
        base.DoSmth();
    }
}

public class Test
{
    public void Show()
    {

    }

    private BaseFoo<baseOutput, baseInput> CreateInstance()
    {
        return new Bar(); // Error right here
    }
}
like image 627
Nexor Avatar asked May 28 '16 08:05

Nexor


2 Answers

I'll give you an example of why you're prevented from doing that.

Imagine instead, your classes were written like this:

public class BaseFoo<TOutput, TInput>
    where TOutput : BaseOutput
{
    public TOutput Something { get; set; }
}

public class Bar : BaseFoo<ExtendOutput, ExtendInput>
{

}

public class BaseInput { }
public class BaseOutput { }
public class ExtendOutput : BaseOutput { }
public class SomethingElse : BaseOutput { }

Now, you have this method:

private BaseFoo<BaseOutput, BaseInput> CreateInstance()
{
    //At this point, Something will be of type ExtendOutput.
    return new Bar();
}

So, we call it like this:

var myBar = CreateInstance();

Now, mybar.Something is of type BaseOutput. That's fine, though, because ExtendOutput : BaseOutput, right? Not quite.

What happens when we do this:

myBar.Something = new SomethingElse();

That's valid, because Something expects a BaseOutput, and SomethingElse is a BaseOutput. However, the object is actually a Bar, which explicitly says it should be an ExtendOutput.

The problem is clearer if we attempt to cast it back:

var myBaseFoo = CreateInstance();
myBaseFoo.Something = new SomethingElse();
Bar myBar = (Bar)myBaseFoo;
myBar.Something; // Here, we're told it's going to be an `ExtendOutput`, 
                 // but we get a `SomethingElse`?

That's clearly wrong. And that's why you're prevented from doing what you're trying to do. You can have this behavior with covariance.

Covariance makes it illegal to pass in a TOutput. So, this line

public TOutput Something { get; set; }

Would be invalid. We would only be allowed to expose the getter:

public TOutput Something { get; }

Which alleviates the above problem

like image 83
Rob Avatar answered Nov 10 '22 23:11

Rob


Bar is BaseFoo<ExtendOutput, ExtendInput>, and CreateInstance() requires BaseFoo<baseOutput, baseInput> to be returned, so it can't return Bar which is BaseFoo<ExtendOutput, ExtendInput>.

Regardless ExtendOutput inherits baseOutput, when you inherit a generic class the inheritance is invariant.

Consider using interfaces with in and out generic modifiers:

public class baseOutput
{
    public string output;
}

public class baseInput
{
    public string input;
}

public class ExtendOutput : baseOutput
{
    public long id;
}

public class ExtendInput : baseInput
{
    public long id;
}

public interface IBaseFoo<out T1, out T2>
{
    public void DoSmth();
}

public class Bar : IBaseFoo<ExtendOutput, ExtendInput>
{
    public void DoSmth()
    {

    }
}

public class Test
{
    public void Show()
    {

    }

    private IBaseFoo<baseOutput, baseInput> CreateInstance()
    {
        return new Bar();
    }
}
like image 32
enkryptor Avatar answered Nov 11 '22 01:11

enkryptor