ねののお庭。

かりかりもふもふ。

【C#】open/closed type 及び bound/unbound type の定義。

generic type における open type と unbound type の違い、あるいは closed type と bound type の違いを明確に説明できますか?

この記事では open/closed type 及び bound/unbound type の厳密な言葉の定義について取り扱います。 とはいえ、ぶっちゃけ厳密な定義を知らなくても 99.999% 問題になる事はないでしょう。 そもそも普通に C# コード書いている分には bound/unbound type という言葉は登場すらしないと思われます。 役立つのは Roslyn 弄っている人間くらいでしょうか...。

C# の言語仕様

C# の言語仕様は ECMA にて標準化されています。 それが ECMA-334 です。 最終更新が 2023/12 と比較的最近で、C# 7 の仕様までが標準化されています。 なお、この記事執筆時点では C# 12 が最新なので、控えめに言って古め。

とはいえ、この手の標準化された仕様書は非常に有益です。 エンドユーザー向けのドキュメント等では語られない細かい事柄についてでも、この手の仕様書には詳細に書かれていますからね。

という事で、open/closed type とか bound/unbound type についても書かれているでしょう、と ECMA-334 を読みに行くとバッチリ書いてあります。generic type が C# に導入されたのは C# 2 ですからね、記述されていて当然でしょう。 これからそれらを読み解いていきます。 なおこれ以降は全て ECMA-334 の 7th edition を参照しているものとします。

Constructed types

open/closed type 及び bound/unbound type について理解するために、まずは constructed type について理解しておく必要があります。 以下は ECMA-334 の 8.4 Constructed types の冒頭からの引用。

A generic type declaration, by itself, denotes an unbound generic type that is used as a “blueprint” to form many different types, by way of applying type arguments. The type arguments are written within angle brackets (< and >) immediately following the name of the generic type. A type that includes at least one type argument is called a constructed type. A constructed type can be used in most places in the language in which a type name can appear. An unbound generic type can only be used within a typeof_expression (§12.8.17).

generic type declaration それ自体が unbound generic type を示します。 そしてその unbound generic type は type arguments を適用することを通じて、多くの異なる types の「設計図」として使用されます。 一文目を雑に訳すとこんなところでしょうか。 まず初めに unbound generic type という語彙がどのようなものであるかが解説された形ですね。denotes という単語が使われているのが地味に大切だと思います。 その後に type arguments をどのように書くかが記述されていますが、まぁこれは皆さんご存じの通り。

そしてその後に大事な A type that includes at least one type argument is called a constructed type. という一文が続いています。 少なくとも 1 つの type argument を含む type を constructed type と呼ぶよ、と書かれています。 ちなみに parameter と argument は明確に違うものなので注意してください。 ここで言ってる type argument は実型引数の事です。

  • parameter : 仮引数
  • argument : 実引数

そして最後に constructed type は言語の殆どの場所で利用する事ができる一方で、unbound generic type は typeof expression でしか使えない、と書かれています。

Open and closed types

以下は ECMA-334 での 8.4.3 Open and closed types からの引用。

All types can be classified as either open types or closed types. An open type is a type that involves type parameters. More specifically:

- A type parameter defines an open type.
- An array type is an open type if and only if its element type is an open type.
- A constructed type is an open type if and only if one or more of its type arguments is an open type. A constructed nested type is an open type if and only if one or more of its type arguments or the type arguments of its containing type(s) is an open type.

A closed type is a type that is not an open type. At run-time, all of the code within a generic type declaration is executed in the context of a closed constructed type that was created by applying type arguments to the generic declaration. Each type parameter within the generic type is bound to a particular run-time type. The run-time processing of all statements and expressions always occurs with closed types, and open types occur only during compiletime processing.

All types can be classified as either open types or closed types. という文から始まります。 全ての type は open type もしくは closed type に分類されるぞ、と書かれているわけですが、これはとても地味ではありながら非常に大切な情報です。

で、その肝心の open type とはなんぞや、というのが以下のような形で記述されています。

  • type parameter は open type を定義する。
    • 「type parameter を open type と定義する」でも「type parameter は open type である」でもないので注意。
  • element type が open type な配列は open type である。
  • type arguments の1つ以上が open type の場合、その constructed type は open type である。constructed nested type は自身の type arguments の1つ以上 open type もしくは containing type(s) の type arguments が open type の場合、open type。

そして続く文が A closed type is a type that is not an open type. です。なるほど。

Bound and unbound types

続いて bound/unbound types について。 以下は ECMA-334 の 8.4.4 Bound and unbound types からの引用。

The term unbound type refers to a non-generic type or an unbound generic type. The term bound type refers to a non-generic type or a constructed type.

An unbound type refers to the entity declared by a type declaration. An unbound generic type is not itself a type, and cannot be used as the type of a variable, argument or return value, or as a base type. The only construct in which an unbound generic type can be referenced is the typeof expression (§12.8.17).

上記に沿えば、以下のような分類になっている事が分かります。non-generic type は unbound type であり、bound type でもある、という点は面白いですね。

  • unbound type: non-generic type / unbound generic type
  • bound type: non-generic type / constructed type

そして続く文は An unbound type refers to the entity declared by a type declaration. は要するに「unbound type は、type declaration で宣言された entity を参照する」と言っているわけです。この refers to the entity というのが地味に大切だと思います。

そして次の An unbound generic type is not itself a type, and cannot be used as the type of a variable, argument or return value, or as a base type. という文は極めて重要です。 とくに重要なのは An unbound generic type is not itself a type、つまり「unbound generic type それ自身は type ではない」という点です。 Open and closed types の項で「全ての type は open type もしくは closed type に分類される」というのがありましたが、「unbound generic type それ自身は type ではない」ので、open type でもなければ、closed type でもないといえます。

そして次に The only construct in which an unbound generic type can be referenced is the typeof expression とあります。 これ、逆にいえば typeof expression 以外の箇所で記述されているものはなんでも unbound generic type ではない、という事が分かります。

まとめると

上記を一通り読む事によって以下の語彙がどのような意味を持つか、明らかになりました。

  • unbound generic type
  • constructed type
  • open type
  • closed type
  • unbound type
  • bound type

が、正直具体例をみないとイマイチ頭に入ってこないでしょう。 ということで具体例を以下にのせます。

// generic type declaration が"示している"のは unbound generic type である
// unbound generic type は unbound type である
// unbound type は type ではない。
// type ではないので、open/closed type のどちらでもない。
class MyType<T>
{
}

class MyWrap<T1>
{
    // 少なくとも 1 つの type argument を含んでいるので、constructed type
    // open type の定義に当てはまるため、open type
    // constructed type なので、bound type
    MyType<T1> instance;
}

// type argument がないので、constructed type ではない。
// constructed type ではないので、unbound generic type である。
// MyType<> などという書き方は typeof expression でしか使えないため unbound generic type であるともいえる。
//     これは論理の順序が逆転してしまっているが、まぁ良い事にしましょう...。
// unbound generic type なので、unbound type である。
// unbound type であるから、type ではない。
// type ではないので、open/closed type のどちらでもない。
var t = typeof(MyType<>); 

ECMA 以外の文献

ECMA-334 を読む限りにおいては、open generic type や closed generic type などという言葉は出てきません。 しかしながら、巷ではそのような言葉が比較的頻繁に使われています。

Microsoft のドキュメントでそのような言葉が使われている箇所があるのか検索を書けると、ところどころあります。たとえば Generics and Attributes では以下のように open generic type 及び closed constructed generic type について記述されています。

Attributes can be applied to generic types in the same way as nongeneric types. However, you can apply attributes only on open generic types and closed constructed generic types, not on partially constructed generic types. An open generic type is one where none of the type arguments are specified, such as Dictionary<TKey, TValue> A closed constructed generic type specifies all type arguments, such as Dictionary<string, object>. A partially constructed generic type specifies some, but not all, type arguments. An example is Dictionary<string, TValue>.

また Introducing Generics in the CLR では以下のように記述されています。こちらについては closed generic type という言葉が使われています。

You can see in the Node example that type parameter T can be used alone as in the definition of m_data, and can also be used as part of another type construction as in the case of m_next. Use of a type parameter as an argument to another generic type definition, such as Node, is called an open generic type. The use of a concrete type as a type argument, such as Node<System.Byte>, is called a closed generic type.

Effective C# 6.0/7.0 という本の3章では以下のような文章があります。

ジェネリックな定義はジェネリック型定義と呼ばれます。 すべての型引数が指定されている、特定のジェネリック型のインスタンスは、クローズジェネリック型と呼ばれます。 (一部の型引数が未指定なものはオープンジェネリック型と呼ばれます)

API 的には

Effective C# 6.0/7.0 で登場している「ジェネリック型定義 (generic type definition)」 という言葉は core library の API でも使われており、これは unbound generic type と等しい事が伺えます。

var t1 = typeof(List<int>); // constructed type
var t2 = typeof(List<>); // unbound generic type

Console.WriteLine(t1.GetGenericTypeDefinition() == t2); // true

Roslyn においては以下のようになっています。

Compilation compilation = ...;

var symbol = compilation.GetTypeByMetadataName("System.Collections.Generic.List`1")!;
Console.WriteLine(symbol.IsDefinition); // true
Console.WriteLine(symbol.IsUnboundGenericType); // false
Console.WriteLine(symbol.ToDisplayString()); // System.Collections.Generic.List<T>

var symbol2 = symbol.ConstructUnboundGenericType()!;
Console.WriteLine(symbol2.IsDefinition); // false
Console.WriteLine(symbol2.IsUnboundGenericType); // true
Console.WriteLine(symbol2.ToDisplayString()); // System.Collections.Generic.List<> 

あくまで type declaration によって定義された generic type は unbound generic type を示しているにすぎず、unbound generic type そのものではない、という事なのでしょう。 噛み砕くと、type declaration によって宣言(及び定義)された type と generic type definition は異なり、generic type definition は unbound generic type であり unbound type でもある、といったところでしょうか。

References