2016-07-31 18 views
2

[注意:這個問題已經吸引了低運行時性能的解決方案,所以值得強調的是除了類型安全之外,性能是關鍵。更多性能信息請參見末尾的註釋。]將編譯時安全性添加到C#列表索引器 - 這可能嗎?

我在一個相當複雜的算法中使用List<int>的幾個不同實例。

其中一些列表包含相互間的索引,即它們提供間接級別。

我修復了訪問列表時使用錯誤索引器導致的一些錯誤。因爲所有的列表都是相同的類型,即List<int>,編譯器根本不提供任何類型的安全。例如:

// The below statement is wrong - it should be list1[list2[x]], 
// as x is an index into list2, not list1. 
// list2 returns indexes into list1. 
// But the compiler is oblivious to this. 
// 
var f = list1[x]; 

於是,我就開始想,這可能是通過使用強類型的索引到每個列表,只是換一個整數增加一定程度的類型安全的:

/// An index into the first list 
struct Index1 
{ 
    public int Value { get; set; } 
} 

/// An index into the second list 
struct Index2 
{ 
    public int Value { get; set; } 
} 

然後,聲明正確索引類型的變量將在編譯時捕獲一類錯誤。 (這並非萬無一失,這不是我所追求的 - 更好就是了。)

不幸的是,通用List<T>沒有提供使用自定義索引類型的方法 - 索引器總是一個int類型。

有沒有另一種方法可以完成我想要做的事情?想到一個自定義的集合 - 我不介意的努力,這可能會支付自己。但我想不出可以如圖所示使用的一種。 (當然,我可以爲每個索引器創建一個單獨的集合類型 - 但如果可能的話,我希望使用單個新集合類型,因爲否則代碼複製開始成爲問題。)

性能::該算法被重寫爲使用性能列表。我們甚至考慮使用數組,因爲有一個更少的邊界檢查。因此,任何建議的解決方案都應具有出色的運行時性能 - 至少可以達到List<T>

因此,結構(或任何其他技術)理想情況下應在編譯時類型安全檢查後進行優化。

使用案例: 使用情況是:

  1. 通過一種安全的索引從列表訪問隨機元素。
  2. A for循環列表,也使用類型安全索引器。
  3. 在列表中使用foreach。 List優化其GetEnumerator()以返回一個結構,因此避免分配。我想保留這一點。
+0

澄清上面。 – bright

+0

您是否需要保留排序,或者您是否只需要按索引訪問元素? – driis

+0

您是否意味着在每個系列中訂購 - 是的,這非常重要。爲什麼? – bright

回答

1

你應該可以使用SortedDictionary<TKey, TValue>來做你想做的。確保在您的自定義密鑰類型上實現EqualsGetHashCode

SortedDictionary允許快速和類型安全的隨機訪問元素,同時保持秩序。

如果您需要列表語義以避免重寫太多的代碼,那麼創建一個類似於列表的類型(SortedDictionary作爲後備存儲)並不困難。

+0

你說得對,會有性能損失。這很重要嗎?這取決於你的情況,只有你可以通過基準實際情況來判斷。 – driis

+0

每個訪問的字典查找是不可接受的,恐怕。此外,可能會有對Equals和GetHashCode的虛擬方法訪問,甚至可能會在該過程中對結構進行裝箱。是的,我在OP中表示,性能是關鍵。這是問題的一個參數。 – bright

+0

噢,如果TKey是一個結構體,我認爲你可以在沒有重載的情況下做到這一點,它應該關注你的一些保留......也許參見http://stackoverflow.com/a/5927853/13627 – driis

0

也許你可以包裝一個列表類似的集合類的東西,提供了一個通用的索引類型:

public class IndexedCollection<TIndex, TValue> : IList<TValue> 
{ 
    private List<TValue> _innerList; 
    private Func<TIndex, int> _getIndex; 

    public IndexedCollection(Func<TIndex, int> getIndex) 
    { 
     this._getIndex = getIndex; 
     this._innerList = new List<TValue>(); 
    } 

    new public TValue this[TIndex indexObj] 
    { 
     get 
     { 
      var realIndex = this._getIndex(indexObj); 
      return _innerList[realIndex]; 
     } 
     set 
     { 
      _innerList[this._getIndex(indexObj)] = value; 
     } 
    } 

    // All the other members 
} 

...然後實例是這樣的:

var list1 = new IndexedCollection<Index1, Foo>(i => i.Value); 
var list2 = new IndexedCollection<Index2, Foo>(i => i.Value); 
+0

對getIndex()的函數調用不會被優化,所以這將不符合規定的性能要求。 – bright

+0

我不認爲需要優化才能表現出色。與字典一樣,它的成本比做散列還要便宜。但是,顯然,不會像直接列表訪問那樣執行。 – Jacob

1

我覺得你遇到的根本問題是,在你的結構中,集合沒有辦法讓一個通用的參數,然後得到Value而不添加一些接口在兩個結構(導致虛擬呼叫),Func<TIndex, int>(如Jacob's answer一樣),或者使用更復雜的集合來抽象出「索引」的概念。

爲每個Index*實現完整集合的直接方法不一定會導致代碼重複。代碼生成或IL生成有幾種方法。下面是與一個索引類沿每個索引結構生成代碼design-time T4 template

<#@ template debug="false" hostspecific="false" language="C#" #> 
<#@ assembly name="System.Core" #> 
<#@ output extension=".cs" #> 
using System.Collections.Generic; 

<# 
string[] indexTypeNames = { "Index1", "Index2" }; 

foreach (var name in indexTypeNames) 
{ 
    string collectionName = name + "List"; 
#> 
public struct <#= name #> 
{ 
    public int Value { get; set; } 
} 

public class <#= collectionName #><T> 
{ 
    private List<T> _inner = new List<T>(); 

    public T this[<#= name #> index] 
    { 
     get { return _inner[index.Value]; } 
     set { _inner[index.Value] = value; } 
    } 

    // Other desired IList<T> methods. 
} 

<# 
} 
#> 

當T4模板是建立,它生成此代碼文件:

using System.Collections.Generic; 

public struct Index1 
{ 
    public int Value { get; set; } 
} 

public class Index1List<T> 
{ 
    private List<T> _inner = new List<T>(); 

    public T this[Index1 index] 
    { 
     get { return _inner[index.Value]; } 
     set { _inner[index.Value] = value; } 
    } 

    // Other desired IList<T> methods. 
} 

public struct Index2 
{ 
    public int Value { get; set; } 
} 

public class Index2List<T> 
{ 
    private List<T> _inner = new List<T>(); 

    public T this[Index2 index] 
    { 
     get { return _inner[index.Value]; } 
     set { _inner[index.Value] = value; } 
    } 

    // Other desired IList<T> methods. 
} 

很明顯,你會想接口的實現集合,所以他們有一個自然的API,但很清楚你可以如何添加這些。

由於代碼是在設計時生成的,所以重複不是維護問題。但是,我會將代碼gen和IL gen分類爲現有類型系統無法表達的東西的解決方法,因此我還希望僅使用泛型可以看到更好的答案。

+0

欣賞答案,以及對性能的關注和對更優雅的解決方案的期望:) – bright

相關問題