C# 9.0より前では、オーバライド元のメソッドとオーバライドしたメソッドで、返り値型は同じ型にしないといけませんでした。そのため、以下のコードはC# 9.0より前ではコンパイルエラーとなりました。メソッドをオーバーライドした時、返り値型を派生型にできなかったからです。
ところがC# 9.0で、共変戻り値型が新機能として加わり、メソッドのオーバライドで返り値型を元の返り値型の派生型にできるようになりました。そのため、以下のコードはC# 9.0では、コンパイルエラーにならず問題なく動作します。
public class Enemy
{
/* 略 */
}
public class BossEnemy : Enemy
{
/* 略 */
}
public class EnemyFactory
{
public virtual Enemy Create() { return new Enemy(); }
}
public class BossEnemyFactory : EnemyFactory
{
// C# 9.0より前ではコンパイルエラー
// C# 9.0からは正常なコード
// BossEnemyFactoryを使う際はこう書けた方が便利なことがある
public override BossEnemy Create () { return new BossEnemy(); }
// C# 9.0より前ではこうするしかなかった。
// public override Enemy Create () { return new BossEnemy(); }
}
実際のアプリケーションコードを書く場合、「この機能を全く使わないよー」という人もいるかもしれません。しかし、フレームワーク・ライブラリだと活用されるでしょう。
ちなみにJavaでは以前から「共変戻り値型」が普通に使えます。Java・Androidのフレームワークにおける、「共変戻り値型」が活用されている例を紹介します。
ViewGroupという型には、次のような派生型があります。
などです。
ViewGroupには、ViewGroup.LayoutParams を返す、generateLayoutParamsというメソッドを持っています。このメソッドは各種派生型において、共変戻り値型を活用しオーバーライドされています。次の表のように、ViewGroupの各種派生型において、generateLayoutParamsメソッドの返り値型は、ViewGroup.LayoutParams の派生型になっています。これが実現できるのは、Javaが「戻り値共変型」機能をサポートしているからです。
派生型 | generateLayoutParamsの返り値型 |
---|---|
FrameLayout | FrameLayout.LayoutParams |
RelativeLayout | RelativeLayout.LayoutParams |
LinearLayout | LinearLayout.LayoutParams |
C#でもライブラリ・フレームワークを中心に活用が期待されます。
公式ドキュメントのサンプルによると、
たとえば、Roslyn コードベースでは、次のようになります。
とあります。
class Compilation ...
{
public virtual Compilation WithOptions(Options options)...
}
class CSharpCompilation : Compilation
{
public override CSharpCompilation WithOptions(Options options)...
}
Compilation型は、派生型にCSharpCompilationやVisualBasicCompilationを持つ型です。
CSharpCompilation型において、WithOptionsをオーバーライドする際、返り値型をCSharpCompilationにしています。CSharpCompilationという型の意味を考えると、WithOptionsという名前のメソッドで返すのがCSharpCompilationになるのは、納得ができます。しかしC# 9.0以前では、返り値共変型を備えていないため、抽象型であるCompilationを返すしかありませんでした。
なおこれは、「もし今から新規実装するならば」という例だと思われます。実際のRoslynのコード(v3.7.0-3.20312.3)では、以下のように共変戻り値型は使われていません。
- https://github.com/dotnet/roslyn/blob/v3.7.0-3.20312.3/src/Compilers/Core/Portable/Compilation/Compilation.cs#L336-L346
- https://github.com/dotnet/roslyn/blob/v3.7.0-3.20312.3/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs
- https://github.com/dotnet/roslyn/blob/v3.7.0-3.20312.3/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs#L583
- https://github.com/dotnet/roslyn/blob/v3.7.0-3.20312.3/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs#L3260-L3263
一見地味な機能ですが、「共変戻り値型」が導入されたことで、フレームワークやライブラリ開発者の、「あぁ、ここもっとこう書きたいのに」が減ったことでしょう。
もちろん、「共変戻り値型」はフレームワークやライブラリだけでなく、アプリケーションのロジックの記述でも活用できるでしょう。