2015-09-22 16 views
15

私はLEDを表すクラスを書いています。 255タイプがこの一般的なクランプ方法のために推論されないのはなぜですか?

から0の範囲内のR、G、Bのための基本的3 uint値私はC#に新たなんだと私がしたいことを8ビットよりも大きいのuint 、始まりました。自分のクランプメソッドを書く前に、私はオンラインで1つを探して、this great looking answerが拡張メソッドを提案していることを発見しました。問題は、タイプがuintであると推論できないことです。どうしてこれなの?このコードにはすべてが書かれています。私はそれを動作させるために型を明示的に与える必要があります。 byteを使用して

class Led 
{ 
    private uint _r = 0, _g = 0, _b = 0; 

    public uint R 
    { 
     get 
     { 
      return _r; 
     } 
     set 
     { 
      _r = value.Clamp(0, 255); // nope 

      _r = value.Clamp<uint>(0, 255); // works 
     } 
    } 
} 

// https://stackoverflow.com/a/2683487 
static class Clamp 
{ 
    public static T Clamp<T>(this T val, T min, T max) where T : IComparable<T> 
    { 
     if (val.CompareTo(min) < 0) return min; 
     else if (val.CompareTo(max) > 0) return max; 
     else return val; 
    } 
} 

間違い

は、当然のことながら移動するための方法です。しかし私はまだ質問に対する答えに興味があります。

+0

ちょうどメモとして、私はこのジェネリックをもう少し制限しようとします。 'IComparable 'を実装するすべての型が 'Clamp'関数を得る方法は、意味的には正しくない可能性があります。これは、Tがisa(byte、sbyte、int、uint)のような特定の型に 'T 'を制限することができないという状況の1つです。 –

+1

@RonBeyer Eric Lippertによると、' IComparable 'インターフェースは総注文を提供する必要があります。それで、なぜ「クランプ」は、たとえ文字列上であっても、意味的に正しいわけではないのですか? – Rawling

+0

@Rawling例えば、 'string'は' IComparable 'を実装しています。これは' Clamp'(意味はありますが、文字列ではありません)に対して意味的に正しいものではありません。 –

答えて

22

0255を使用しているのは、intの値で、uintではありません。 C#の素数は、intの範囲内に収まる場合は、常にintの値として扱われます。

Clampは、uint.Clamp(int, int) => uintという形式で呼び出されています。これはコンパイラによってClamp(unit, int, int) => uintに変換されます。コンパイラは実際にはClamp(T, T, T) => Tを期待しているので、uintintの種類が混在しているため、エラーが報告されます。どのタイプを解決するのが妨げられますか。T

変更行:

_r = value.Clamp(0, 255); 

へ:

_r = value.Clamp(0U, 255U); 

とコードがコンパイルされます。 U接尾辞は、数値がuintの値であることをコンパイラーに通知します。

+1

しかしこれは 'uint'がうまくいけば' int'を自動的に使わない理由を説明していません。これはコンパイルされません: '_r = value.Clamp((int)-1、(int)-22);'なぜでしょうか? 'int'キャストは、数値が' int'であることをコンパイラに伝えます、なぜそれがコンパイルされないのですか? –

+2

@MatthewWatson - 'value'は' uint'なので? – Corak

+0

@MatthewWatson Tと戻り値の型が等しい必要があるためです。 'r'は' uint'で、二つのパラメータは 'int'なので、あなたが望むものを推論することはできません。 –

16

あなたは引数を指定してClamp<T>(T, T, T)を呼び出しているuint, int, int0255intリテラルあるとして)。他の1つのタイプからのimplicit conversionがないので

、コンパイラはTintまたはuintを行うかどうかを考え出すことはできません。

+0

どちらの方向にも暗黙の変換はないが、この場合、最後の2つの引数 '0'と '255'は' int'型のコンパイル時定数であるため、[暗黙の__constant__式変換](https://msdn.microsoft.com/da-dk/library/aa691286.aspx)_from_ 'int' _to_' uint'。そうでない場合、どのように 'value.Clamp (0、255)'が動作するのでしょうか?ジェネリックメソッドの 'T'を明示的に与えると、その暗黙の定数式の変換が適用されます。しかし、詳細については、Lippertの新しい答えを見てください。 –

4

整数リテラルに接尾辞がない場合、その型はint、uint、long、ulongの値を表すことができる最初の型です。あなたは0と255を使用しています。これはintにうまく行き、1つが選択されます。

あなたは、単にリテラル

_r = value.Clamp(0U, 255U); 

詳しい情報はdocumentationから見つけることができる接尾辞を付けることでuintを使用するようにコンパイラに指示することができます。

20

他の回答は正確ですが、ここで私が特に呼び出されるべきだと思う微妙な点があります。

通常、C#では、整数リテラルのタイプはintですが、定数が範囲内にある任意の数値タイプに暗黙的に変換される可能性があります。したがって、intは暗黙的にuintに変換可能ではありませんが、myuint = 123;は、intが当てはまるため、合法です。

この事実から、intリテラルは、uintが期待される場所であればどこでも使用できますが、なぜその信念が偽であるかがわかりました。

タイプ推論アルゴリズムはこのようになります。 (これはもちろん、大規模な単純化である。ラムダは、これはかなり複雑にする。)

  • 計算の引数
  • の種類は種類を推測、引数とその分析から、対応する仮パラメータ
  • 間の関係を分析します完全なタイプのすべてのタイプのパラメータにバインドが必要であり、一貫性の境界が矛盾してはならないことを確認します。推論が不完全または不一致の場合、この方法は適用できません。
  • 推定された型が制約に違反する場合、このメソッドは適用できません。
  • それ以外の場合は、推定された型のメソッドがオーバーロード解決に使用されるメソッドのセットに追加されます。

オーバーロードの解決は、候補セット内のメソッドを互いに比較して最良のものを見つけることに進みます。

(戻り型がどこに考えないことは勿論であることに注意してください。C#のチェックを戻り型がオーバーロード解決がないオーバーロード解決の間、方法を選択した後にそれがに割り当てられているどのように割り当てることができるかどうかを確認する)

あなたのケースでは、「一貫した境界のセットがあることを確認する」ステップでタイプ推論が失敗しています。 Tは、intuintの両方にバインドされています。これは矛盾しているので、オーバーロード解決を考慮する方法のセットにメソッドが追加されることはありません。 int引数に変換可能であるという事実は、uintに変換されることはありません。型推論エンジンは型だけで動作します。

タイプ推論アルゴリズムは、シナリオ内でどのような方法でも「バックトラック」しません。 「OK、私はTの一貫した型を推論することはできませんが、おそらく個々の型のうちの1つが機能します。intuintの両方の境界を試してみたらどうでしょうか? " (の場合は、lambdaが関わっているときと似たようなことをするため、いくつかのシナリオでは、可能な限り多くの種類の組み合わせを試すことができます。)推論アルゴリズムがそのように働くと、望む結果が得られます。ではない。

基本的にここで哲学は型推論アルゴリズムはにプログラム作業を作るためにどのような方法を見つけるために求めていないことですが、むしろから得られた情報から独自の論理的な結論を導き出すの種類についての推論のチェーンを見つけるために、引数。 C#は、ユーザーが何をするのかを試みるだけでなく、推測を避けることも試みます。この場合、潜在的に間違って推測するのではなく、推測するタイプについて明確にする必要があります。

関連する問題