C'è uno scenario ragionevole per un IComparer stateful ?
Domanda
Non ho mai scritto una IComparer<T>
stateful con un costruttore di default. Tutte le biblioteche implementazioni standard che ho controllato in Reflector sono apolidi pure. Pertanto, mi piacerebbe pensare che posso liberamente in cache IComparer<T>
in questo modo:
PriorityQueue<TPriority, TComparer> where TComparer : IComparer<TPriority>, new()
{
private static TComparer _comparer = new TComparer();
public PriorityQueue() {...}
...
}
anziché
PriorityQueue<TPriority>
{
private IComparer<TPriority> _comparer;
public PriorityQueue(IComparer<TPriority> comparer) {
_comparer = comparer;
...
}
...
}
Quindi, ecco la domanda: avete mai scritto / visto un IComparer<T>
per il quale questo sarebbe abbattere? Se sì, come comune è?
EDIT: Il motivo per cui non voglio il sovraccarico della seconda versione in questo caso è che la struttura dei dati è persistente. Si è implementato come un albero in cui i nodi hanno alcun riferimento genitore / root. Quindi non sarebbe stato un riferimento a di confronto per coda, ma un riferimento a di confronto per nodo! Il mio progetto originale era solo per usare IComparable<T>
e raccomandare a scrivere una struct wrapper per i confronti personalizzati.
Soluzione
Bene, avere un static di confronto significa che non si possono avere i confronti differenti su diverse code; questo può essere un problema ... di tanto in tanto la gente do bisogno di un confronto personalizzato; per esempio, se non controllano il tipo. Il mio approccio di default potrebbe essere:
PriorityQueue<TPriority>
{
private IComparer<TPriority> _comparer;
public PriorityQueue(IComparer<TPriority> comparer) {
_comparer = comparer;
...
}
public PriorityQueue() : this(Comparer<T>.Default) {}
}
Re comparatori stateful; Sì, ho scritto diversi - in particolare per la scrittura di comparatori di proiezione in stile LINQ ... per esempio, qualcosa come:
public static class ProjectionComparer<TSource>
{
public static IComparer<TSource> CompareBy<TValue>(
Func<TSource, TValue> selector)
{
return CompareBy<TValue>(selector, Comparer<TValue>.Default);
}
public static IComparer<TSource> CompareBy<TValue>(
Func<TSource, TValue> selector,
IComparer<TValue> comparer)
{
return new ProjectionComparerItem<TValue>(
selector, Comparer<TValue>.Default);
}
class ProjectionComparerItem<TValue> : IComparer<TSource>
{
private readonly IComparer<TValue> comparer;
private readonly Func<TSource, TValue> selector;
public ProjectionComparerItem(
Func<TSource, TValue> selector,
IComparer<TValue> comparer)
{
this.selector = selector;
this.comparer = comparer;
}
public int Compare(TSource x, TSource y)
{
// TODO: some null stuff...
return comparer.Compare(selector(x), selector(y));
}
}
}
che consente di:
IComparer<Customer> comparer = ProjectionComparer<Customer>
.CompareBy(cust => cust.Name);
istanza "Ordina per nome" confronto.
Altri suggerimenti
Sì, ho, ma penso che sia abbastanza raro.
Ci sono rari casi in cui si desidera implementare un confronto che è dipendente da altri dati. Per esempio, abbiamo alcune routine spaziali in cui nutriamo un asse che viene utilizzato per il confronto come parte della IComparer.
Detto questo, è abbastanza facile per risolvere questo da solo utilizzando una classe di confronto separato, e questo è probabilmente una migliore progettazione in molti modi. Si sta mettendo una limitazione sull'attuazione IComparer<T>
che non ha bisogno di esistere, però, quindi vorrei documentare la vostra logica.
La mia preferenza personale sarebbe quello di rendere il IComparer<T>
non statico, e di fornire due costruttori - uno che prende un'istanza esterna e uno che crea un operatore di confronto di default. Hai il sovraccarico di un operatore di confronto per coda, ma questo è piuttosto minimale (quasi 0 se si dispone di nessuno stato, dal momento che è un solo riferimento a un oggetto a un oggetto "vuoto").
Un altro approccio possibile:
private class PriorityQueueImpl<TPriority, TComparer> where TComparer : IComparer<TPriority> {
// all methods accept a TComparer
// generic in TComparer to avoid boxing for struct TComparers and permit inlining for sealed TComparers
}
public struct PriorityQueue<TPriority, TComparer> where TComparer : IComparer<TPriority> {
private readonly PriorityQueueImpl<TPriority, TComparer> _impl;
private readonly TComparer _comparer;
// methods delegate to _impl
}
io possa andare con lui, almeno per alcuni casi.