簡體   English   中英

C#構造函數constratint到支持比較運算符的類型

[英]C# Constructor constratint to types that support comparison operators

如何創建一個類來存儲任何類型的范圍,前提是該類型允許比較運算符確保提供給構造函數的第一個值小於第二個值?

public class Range<T> where T : IComparable<T>
{
    private readonly T lowerBound;
    private readonly T upperBound;

    /// <summary>
    /// Initializes a new instance of the Range class
    /// </summary>
    /// <param name="lowerBound">The smaller number in the Range tuplet</param>
    /// <param name="upperBound">The larger number in the Range tuplet</param>
    public Range(T lowerBound, T upperBound)
    {
        if (lowerBound > upperBound)
        {
            throw new ArgumentException("lowerBlound must be less than upper bound", lowerBound.ToString());
        }

        this.lowerBound = lowerBound;
        this.upperBound = upperBound;            
    }

我收到錯誤:

Error   1   Operator '>' cannot be applied to operands of type 'T' and 'T'  C:\Source\MLR_Rebates\DotNet\Load_MLR_REBATE_IBOR_INFO\Load_MLR_REBATE_IBOR_INFO\Range.cs   27  17  Load_MLR_REBATE_IBOR_INFO

你可以用

where T : IComparable<T>

...或者您可以在代碼中使用IComparer<T> ,默認為Comparer<T>.Default

后一種方法是有用的,因為它允許甚至對於彼此不能自然比較的類型來指定范圍,但是可以以定制的,合理的方式進行比較。

另一方面,它確實意味着您不會在編譯時捕獲無法比較的類型。

(順便說一下,創建一個范圍類型引入了一系列有趣的API決定,圍繞你是否允許反轉范圍,你是如何跨越它們等等。去過那里,做到這一點,對結果從未完全滿意......)

您不能約束T來支持給定的一組運算符,但您可以約束到IComparable<T>

where T : IComparable<T>

這至少允許你使用first.CompareTo(second) 您的基本數字類型,加上字符串,DateTimes等,實現此接口。

為了結合已經給出的兩個建議,我們將創建Ranges的能力與手動定義的比較規則結合起來,對那些實現IComparable<T>類型進行了IComparable<T> ,並且對后者具有編譯時安全性。

我們采用與靜態Tuple類' Create方法相同的方法。 這也可以讓我們依賴類型推斷簡潔:

public static class Range // just a class to a hold the factory methods
{
    public static Range<T> Create<T>(T lower, T upper) where T : IComparable<T>
    {
        return new Range<T>(lower, upper, Comparer<T>.Default);
    }
    //We don't need this override, but it adds consistency that we can always
    //use Range.Create to create a range we want.
    public static Range<T> Create<T>(T lower, T upper, IComparer<T> cmp)
    {
        return new Range<T>(lower, upper, cmp);
    }
}
public class Range<T>
{
    private readonly T lowerBound;
    private readonly T upperBound;
    private readonly IComparer<T> _cmp;
    public Range(T lower, T upper, IComparer<T> cmp)
    {
        if(lower == null)
            throw new ArgumentNullException("lower");
        if(upper == null)
            throw new ArgumentNullException("upper");
        if((_cmp = cmp).Compare(lower, upper) > 0)
            throw new ArgumentOutOfRangeException("Argument \"lower\" cannot be greater than \"upper\".");
        lowerBound = lower;
        upperBound = upper;
    }
}

現在我們不能意外地使用默認比較器構造一個Range ,它將無法工作,但是也可以省略比較器,只有在它工作時才進行編譯。

編輯:

有兩種主要方法可以在.NET中以訂單授予方式使項目具有可比性,並且這兩種方法都使用。

一種方法是讓一個類型定義它與另一個相同類型的對象*進行比較。 這是由IComparable<T> (或非通用的IComparable ,但是你必須在運行時捕獲類型不匹配,所以它在.NET1.1之后不那么有用)。

例如, int實現IComparable<int> ,這意味着我們可以執行3.CompareTo(5)並接收一個負數,表示當兩者按順序排列時,3位於5之前。

另一種方法是擁有一個實現IComparer<T>的對象(同樣是一個在.NET1.1之后不太有用的非通用IComparer )。 這用於比較兩個對象,通常與比較器的類型不同 我們明確地使用它,因為我們感興趣的類型沒有實現IComparable<T>或者因為我們想要覆蓋默認的排序順序。 例如,我們可以創建以下類:

public class EvenFirst : IComparer<int>
{
  public int Compare(int x, int y)
  {
    int evenOddCmp = x % 2 - y % 2;
    if(evenOddCmp != 0)
      return evenOddCmp;
    return x.CompareTo(y);
  }
}

如果我們使用它來對整數列表進行排序list.Sort(new EvenFirst()) ),它會將所有偶數放在第一位,所有奇數放在最后,但是在它們的正常順序中有偶數和奇數塊。

好的,現在我們有兩種不同的比較給定類型實例的方法,一種是由類型本身提供的,一種是“最自然的”,這很好,一種給我們更多的靈活性,也很棒。 但這意味着我們必須編寫任何關注此類比較的代碼片段的兩個版本 - 一個使用IComparable<T>.CompareTo() ,另一個使用IComparer<T>.Compare()

如果我們關心兩種類型的對象會變得更糟。 然后我們需要4種不同的方法!

解決方案由Comparer<T>.Default 這個靜態屬性為我們提供了IComparer<T>.Compare()的實現,用於調用IComparable<T>.CompareTo的給定T

所以,現在我們通常只編寫我們的方法來使用IComparer<T>.Compare() 提供使用CompareTo進行最常見比較的版本只是使用默認比較器的覆蓋問題。 例如,而不是:

public void SortStrings(IComparer<string> cmp)//lets caller decide about case-sensitivity etc.
{
//pretty complicated sorting code that uses cmp.Compare(string1, string2)
}
public void SortStrings()
{
//equally complicated sorting code that uses string.CompareTo()
}

我們有:

public void SortStrings(IComparer<string> cmp)//lets caller decide about case-sensitivity etc.
{
//pretty complicated sorting code that uses cmp.Compare(string1, string2)
}
public void SortStrings()
{
  SortStrings(Comparer<string>.Default);//simple one-line code to re-use all the above.
}

正如您所看到的,我們在這里擁有兩全其美。 只想要默認行為的人會調用SortStrings() ,有人想要使用更具體的比較規則,例如SortStrings(StringComparer.CurrentCultureIgnoreCase) ,並且實現只需做一些工作就可以提供這種選擇。

這就是Range的建議。 構造函數總是使用IComparer<T>並始終使用它的Compare ,但是有一個工廠方法使用Comparer<T>.Default調用它來提供其他行為。

注意,我們並不嚴格需要這個工廠方法,我們可以在構造函數上使用重載:

public Range(T lower, T upper)
  :this(lower, upper, Comparer<T>.Default)
{
}

不利的一點是,我們不能在where添加一個where子句來將其限制為可行的情況。 這意味着如果我們使用未實現IComparer<T>類型調用它,我們將在運行時獲得ArgumentException而不是編譯器錯誤。 喬恩的觀點當他說:

另一方面,它確實意味着您不會在編譯時捕獲無法比較的類型。

使用工廠方法純粹是為了確保不會發生這種情況。 就個人而言,我可能只是使用構造函數覆蓋,並嘗試確保不要不恰當地調用它,但我添加了工廠方法的位,因為它確實結合了這個線程上出現的兩件事。

*嚴格來說,沒有什么可以阻止,例如A : IComparable<B> ,但是雖然這在一開始就沒用,但是對於大多數用途,人們也不知道使用它的代碼是否會最終調用a.CompareTo(b)b.CompareTo(a)所以它不起作用,除非我們在兩個類上都這樣做。 在排序中,如果它不能被推到一個共同的基類,它就會變得很亂。

您可以使用在.NET框架中廣泛使用的IComparable接口。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM