同等の暗黙的オペレーター:なぜ彼らは合法なのですか?
-
01-10-2019 - |
質問
アップデート!
以下のC#仕様の一部の解剖をご覧ください。私は何かが欠けているに違いないと思う 自分 この質問で説明している動作は、実際に仕様に違反しているようです。
更新2!
さて、さらに反省して、いくつかのコメントに基づいて、私は今何が起こっているのか理解していると思います。仕様の「ソースタイプ」という言葉は、変換されているタイプを指します から - つまり、 Type2
以下の私の例では、これは単にコンパイラが候補者を定義された2つのオペレーターに絞り込むことができることを意味します(以降 Type2
両方のソースタイプです)。ただし、選択をさらに絞り込むことはできません。したがって、仕様のキーワードは(この質問に適用されるように)です "ソースの種類", 、私は以前に「タイプを宣言する」ことを意味すると誤解した(私は思う)。
元の質問
これらのタイプが定義されているとします。
class Type0
{
public string Value { get; private set; }
public Type0(string value)
{
Value = value;
}
}
class Type1 : Type0
{
public Type1(string value) : base(value) { }
public static implicit operator Type1(Type2 other)
{
return new Type1("Converted using Type1's operator.");
}
}
class Type2 : Type0
{
public Type2(string value) : base(value) { }
public static implicit operator Type1(Type2 other)
{
return new Type1("Converted using Type2's operator.");
}
}
それから私はこれをすると言います:
Type2 t2 = new Type2("B");
Type1 t1 = t2;
明らかにこれは曖昧です。 implicit
オペレーターを使用する必要があります。私の質問は - 私が見ることができないので どれか この曖昧さを解決する方法(私が望むバージョンを明確にするために明示的なキャストを実行できるわけではありません)、それでも上記のクラスの定義はコンパイルします - コンパイラがそれらのマッチングを許可するのはなぜですか implicit
オペレーターはまったく?
解剖
わかりました、これを理解するために、ハンス・パサントが引用したC#仕様の抜粋を踏み出します。
ユーザー定義の変換演算子が考慮されるタイプのdを見つけます。このセットは、s(sがクラスまたは構造体)、sのベースクラス(sがクラスの場合)、t(tがクラスまたは構造体の場合)で構成されています。
私たちは変換しています から Type2
(s) に Type1
(t)。だからここにあるようです d 例には、3つのタイプすべてが含まれます。 Type0
(それはの基本クラスだからです s), Type1
(t) と Type2
(s).
該当するユーザー定義の変換演算子のセットを見つけます。このセットは、uが空になっている場合は、sを含むタイプSからTに変換されるタイプから変換するクラスまたは構造体によって宣言されたユーザー定義の暗黙的変換演算子で構成されています。 、変換は未定義であり、コンパイル時間エラーが発生します。
大丈夫、これらの条件を満たす2つのオペレーターがいます。で宣言されたバージョン Type1
要件を満たしているからです Type1
入っています d そしてそれはから変換します Type2
(明らかに包囲します s) に Type1
(これは明らかに包囲されています t)。のバージョン Type2
また まったく同じ理由で要件を満たしています。それで u これらの両方の演算子が含まれます。
最後に、最も具体的な「ソースタイプ」を見つけることに関して SX のオペレーターの u:
UのオペレーターのいずれかがSから変換された場合、SXはSです。
今、 両方とも のオペレーター u から変換します s - だからこれは私にそれを教えてくれます SX は s.
これはそれを意味しません Type2
バージョンを使用する必要がありますか?
ちょっと待って!よくわかりません!
私はできませんでした それだけ 定義されています Type1
のオペレーターのバージョン、その場合、残りの唯一の候補者は Type1
のバージョン、そしてまだ仕様に応じて SX だろう Type2
?これは、スペックが不可能なものを義務付ける可能性のあるシナリオのように思えます(つまり、変換が宣言されたことが Type2
実際に存在しない場合は使用する必要があります)。
解決
コンバージョンを定義するためだけにコンパイル時間エラーになりたくない そうかもしれない あいまいさを引き起こします。 Type0を変更してダブルを保存し、何らかの理由で署名された整数と署名されていない整数に個別の変換を提供すると仮定します。
class Type0
{
public double Value { get; private set; }
public Type0(double value)
{
Value = value;
}
public static implicit operator Int32(Type0 other)
{
return (Int32)other.Value;
}
public static implicit operator UInt32(Type0 other)
{
return (UInt32)Math.Abs(other.Value);
}
}
これは正常にコンパイルされ、両方の変換を使用することができます
Type0 t = new Type0(0.9);
int i = t;
UInt32 u = t;
ただし、試してみるのはコンパイルエラーです float f = t
暗黙の変換のいずれかを使用して整数型に到達できるため、フロートに変換できるためです。
上記のType0をコンパイルしたいので、実際に使用されているときに、これらのより複雑な曖昧さについてコンパイラに文句を言うだけです。一貫性のために、より単純なあいまいさは、定義するときではなく、使用するポイントでエラーを引き起こすはずです。
編集
Hansは仕様を引用した答えを削除したので、C#Specの部分をすばやく実行して、変換が曖昧であるかどうかを決定します。
- uのオペレーターの最も具体的なソースタイプのSXを見つけます。
- UのオペレーターのいずれかがSから変換された場合、SXはSです。
- それ以外の場合、SXは、Uのオペレーターのターゲットタイプの組み合わせセットで最も包括的なタイプです。ほとんどの包括的なタイプが見つからない場合、変換は曖昧で、コンパイル時間エラーが発生します。
言い換えれば、Sから直接変換する変換を好みます。そうしないと、Sに変換するのが最も簡単なタイプを好みます。どちらの例でも、Sから2つの変換が利用可能になります。からの変換がなかった場合 Type2
, 、からの変換を好むでしょう Type0
1つ以上 object
. 。明らかに変換するのに適した選択肢がない場合は、ここで失敗します。
- Uの演算子の最も具体的なターゲットタイプTXを見つけます:
- UのオペレーターのいずれかがTに変換される場合、TxはTです。
- それ以外の場合、TXはUのオペレーターのターゲットタイプの組み合わせセットで最も包括的なタイプです。最も包括的なタイプがない場合、変換は曖昧で、コンパイル時間エラーが発生します。
繰り返しますが、Tに直接変換することを好みますが、Tに変換するのが最も簡単なタイプに落ち着きます。Danの例では、利用可能なTに2つの変換があります。私の例では、考えられるターゲットは次のとおりです Int32
と UInt32
, 、そしてどちらも他のものよりも良い一致ではないので、これは変換が失敗する場所です。コンパイラには、かどうかを知る方法がありません float f = t
意味 float f = (float)(Int32)t
また float f = (float)(UInt32)t
.
- uがSXからTXに変換されるユーザー定義の変換オペレーターを1つ正確に含んでいる場合、これは最も具体的な変換演算子です。そのようなオペレーターが存在しない場合、またはそのようなオペレーターが複数存在する場合、変換はあいまいで、コンパイル時間エラーが発生します。
Danの例では、SXからTXへの2つの変換が残っているため、ここで失敗します。 SXとTXを決定するときに異なる変換を選択した場合、SXからTXへの変換はできません。たとえば、持っていた場合 Type1a
に由来する Type1
, 、それから私たちはからの変換があるかもしれません Type2
に Type1a
そしてから Type0
に Type1
これらはまだSX = Type2とTX = Type1を与えますが、実際にはType2からType1への変換はありません。これは本当に曖昧なので、これは大丈夫です。コンパイラは、Type2をType1aに変換してからType1にキャストするか、最初にType0にキャストするかどうかを知りません。
他のヒント
最終的には、完全な成功を収めて禁止することはできません。あなたと私は2つのアセンブリを公開することができました。彼らは、私たち自身を更新しながら、お互いのアセンブリを使用し始めることができました。その後、各アセンブリで定義されたタイプ間で暗黙のキャストを提供できます。次のバージョンをリリースする場合にのみ、コンパイル時間ではなく、これをキャッチできます。
禁止できないものを禁止しようとしないことには利点があります。それは明快さと一貫性をもたらすためです(そして、議員にはレッスンがあります)。