98

I'm working with the .NET framework and I really want to be able to make a custom type of page that all of my website uses. The problem comes when I am trying to access the page from a control. I want to be able to return my specific type of page instead of the default page. Is there any way to do this?

public class MyPage : Page
{
    // My own logic
}

public class MyControl : Control
{
    public MyPage Page { get; set; }
}
Kyle Sletten
  • 5,365
  • 2
  • 26
  • 39
  • possible duplicate of [c# covariant return types utilizing generics](http://stackoverflow.com/questions/4348760/c-sharp-covariant-return-types-utilizing-generics) – nawfal Jul 10 '14 at 06:28

9 Answers9

188

UPDATE: This answer was written in 2011. After two decades of people proposing return type covariance for C# they have been implemented. See Covariant Returns in https://devblogs.microsoft.com/dotnet/c-9-0-on-the-record/.


It sounds like what you want is return type covariance. C# does not support return type covariance.

Return type covariance is where you override a base class method that returns a less-specific type with one that returns a more specific type:

abstract class Enclosure
{
    public abstract Animal Contents();
}
class Aquarium : Enclosure
{
    public override Fish Contents() { ... }
}

This is safe because consumers of Contents via Enclosure expect an Animal, and Aquarium promises to not only fulfill that requirement, but moreover, to make a more strict promise: that the animal is always a fish.

This kind of covariance is not supported in C#, and is unlikely to ever be supported. It is not supported by the CLR. (It is supported by C++, and by the C++/CLI implementation on the CLR; it does so by generating magical helper methods of the sort I suggest below.)

(Some languages support formal parameter type contravariance as well -- that you can override a method that takes a Fish with a method that takes an Animal. Again, the contract is fulfilled; the base class requires that any Fish be handled, and the derived class promises to not only handle fish, but any animal. Similarly, C# and the CLR do not support formal parameter type contravariance.)

The way you can work around this limitation is to do something like:

abstract class Enclosure
{
    protected abstract Animal GetContents();
    public Animal Contents() { return this.GetContents(); }
}
class Aquarium : Enclosure
{
    protected override Animal GetContents() { return this.Contents(); }
    public new Fish Contents() { ... }
}

Now you get both the benefits of overriding a virtual method, and getting stronger typing when using something of compile-time type Aquarium.

Todd West
  • 326
  • 1
  • 8
Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • Keep in mind, since `System.Web.UI.Page` is not an abstract class, you'll need to use the `new` operator instead of `override`. – Zhais Apr 18 '11 at 21:41
  • 4
    Is there a rationale for not supporting return-covariance and param-contravariance available to read anywhere? – porges Nov 14 '12 at 05:19
  • 3
    @Porges: We are not required to give rationales for *not* supporting features. Features are expensive and are not-implemented by default. Only when features are thought of, designed, specified, implemented, tested and shipped to customers do they become implemented. – Eric Lippert Nov 19 '12 at 17:04
  • 27
    @EricLippert Being a C# groupie, I'm always curious about the "backstage", so I just have to ask: Was return type covariance ever considered for C# and deemed to have a too low ROI? I understand time and bugdet constraints, but from your "unlikely to ever be supported" statement it sounds like the design team would actually rather NOT have this in the language. On the other side of the fence, Java introduced this language feature in Java 5, so I wonder what's the difference is in the greater, overarching principles that govern the language design process. – Cristian Diaconescu Nov 20 '12 at 10:14
  • 1
    Just stumbled upon the answers to my questions (and possibly yours, too, @Porges): http://stackoverflow.com/a/1320710/11545 – Cristian Diaconescu Nov 20 '12 at 10:16
  • 1
    @CristiDiaconescu: also: http://stackoverflow.com/a/7996843/10311, so there are versioning issues involved – porges Nov 21 '12 at 00:23
  • 2
    This is being considered for C# 7 it seems like. https://github.com/dotnet/roslyn/issues/2136 – Cyral May 01 '15 at 22:47
  • 7
    @Cyral: Correct; it is on the list in the "moderate interest" bucket -- which is where it has been for a long time! It could happen, but I am pretty skeptical. – Eric Lippert May 01 '15 at 23:50
  • 3
    The feature sadly did not make it into C# 7. But there is still an issue tracking it: https://github.com/dotnet/roslyn/issues/357 – Søren Boisen Jun 23 '17 at 09:57
  • @EricLippert But isn't this also working, and therefore compliant with Liskov principle? (not my answer): https://stackoverflow.com/questions/43892239/c-sharp-return-type-covariance-and-liskov-substitution-principle – John V Jan 29 '18 at 17:59
  • 1
    Available now in C# 9 https://devblogs.microsoft.com/dotnet/welcome-to-c-9-0/ – Michael Buen May 21 '20 at 08:48
  • @MichaelBuen: Thanks for the link, but I think you may have jumped the gun with "available now". As Mads notes in the first line of the linked post, this is an early look at proposed features. – Eric Lippert May 22 '20 at 15:44
  • Be cautious, at the moment covariant return types cause problems with assembly scanning. The `Assembly.GetTypes()` method kills CLR. Follow this issue for more details https://github.com/dotnet/runtime/issues/45037. – Laurynas Lazauskas Nov 26 '20 at 20:23
  • I speculate that return type covariance was added to have feature parity with Java, since with future .NET releases some form of Java interop will be introduced. – ceztko Feb 25 '21 at 12:16
  • With .NET Core & .NET 5+, it's much easier to modify the runtime without the risk of breaking every single app on everyone' machines. So they could support this at the CLR level instead of using compiler hack (which introduces all sorts of problems with reflection, language interop, etc). I guess this is one of the reasons that got this feature the green light. – scharnyw Oct 09 '22 at 03:32
10

With interfaces I got around it by explicitly implementing the interface:

public interface IFoo {
  IBar Bar { get; }
}
public class Foo : IFoo {
  Bar Bar { get; set; }
  IBar IFoo.Bar => Bar;
}
ChetPrickles
  • 820
  • 13
  • 8
4

This is a feature for the upcoming C# 9.0 (.Net 5) of which you can download a preview version now.

The following code now builds successfully (without giving: error CS0508: 'Tiger.GetFood()': return type must be 'Food' to match overridden member 'Animal.GetFood()')

class Food { }
class Meat : Food { }

abstract class Animal {
    public abstract Food GetFood();
}

class Tiger : Animal {
    public override Meat GetFood() => default;
}

class Program {
    static void Main() => new Tiger();
}
Neuron
  • 5,141
  • 5
  • 38
  • 59
2

Placing this in the MyControl object would work:

 public new MyPage Page {get return (MyPage)Page; set;}'

You can't override the property because it returns a different type... but you can redefine it.

You don't need covariance in this example, since it is relatively simple. All you're doing is inheriting the base object Page from MyPage. Any Control that you want to return MyPage instead of Page needs to redefine the Page property of the Control

Zhais
  • 1,493
  • 12
  • 18
1

Yes, it supports covariance, but it depends upon the exact thing you are trying to achieve.

I also tend to use generics a lot for things, which means that when you do something like:

class X<T> {
    T doSomething() {
    }

}

class Y : X<Y> {
    Y doSomethingElse() {
    }
}

var Y y = new Y();
y = y.doSomething().doSomethingElse();

And not "lose" your types.

Cade Roux
  • 88,164
  • 40
  • 182
  • 265
0

I will do it in this way:

class R {
    public int A { get; set; }
}

class R1: R {
    public int B { get; set; }
}

class A
{        
    public R X { get; set; }
}

class B : A 
{
    private R1 _x;
    public new R1 X { get => _x; set { ((A)this).X = value; _x = value; } }
}
Indomitable
  • 808
  • 9
  • 9
0

You can access your page from any control by walking up the parent tree. That is

myParent = this;

while(myParent.parent != null)
  myParent = myParent.parent;

*Did not compile or test.

Or get the parent page in the current context (depends on your version).


Then what I like to do is this: I create an interface with the functions I want to use in the control (for example IHostingPage)

Then I cast the parent page 'IHostingPage host = (IHostingPage)Parent;' and I am all set to call the function on the page I need from my control.

Hogan
  • 69,564
  • 10
  • 76
  • 117
0

I haven't tried it, but doesn't this work?

YourPageType myPage = (YourPageType)yourControl.Page;
AGoodDisplayName
  • 5,543
  • 7
  • 35
  • 50
0

Yes. There are multiple ways of doing this, and this is just one option:

You can make your page implement some custom interface that exposes a method called "GetContext" or something, and it returns your specific information. Then your control can simply request the page and cast:

var myContextPage = this.Page as IMyContextGetter;

if(myContextPage != null)
   var myContext = myContextPage.GetContext();

Then you can use that context however you wish.

Tejs
  • 40,736
  • 10
  • 68
  • 86