Pergunta

Digamos que eu queira criar uma classe de coleção que seja segura por threads por padrão.

Internamente, a classe tem um protegido List<T> propriedade chamada Values.

Para iniciantes, faz sentido ter a classe implementar ICollection<T>. Alguns dos membros dessa interface são muito fáceis de implementar; por exemplo, Count retorna this.Values.Count.

Mas implementando ICollection<T> exige que eu implemente IEnumerable<T> assim como IEnumerable (não genérico), o que é um pouco complicado para uma coleção segura por threads.

Claro, eu sempre poderia jogar um NotSupportedException sobre IEnumerable<T>.GetEnumerator e IEnumerable.GetEnumerator, mas isso parece um policial para mim.

Eu já tenho um tópico seguro getValues função que bloqueia Values e retorna uma cópia na forma de um T[] variedade. Então minha ideia era implementar GetEnumerator retornando this.getValues().GetEnumerator() para que o código a seguir fosse realmente seguro para threads:

ThreadSafeCollection coll = new ThreadSafeCollection ();

// add some items to coll

foreach (T value in coll) {
    // do something with value
}

Infelizmente, essa implementação só parece funcionar para IEnumerable.GetEnumerator, não a versão genérica (e assim o código acima lança um InvalidCastException).

Uma ideia que tive, que parecia funcionar, era lançar o T[] Valor de retorno de getValues para um IEnumerable<T> antes de ligar GetEnumerator nele. Uma alternativa seria mudar getValues para devolver um IEnumerable<T> em primeiro lugar, mas depois para o não genérico IEnumerable.GetEnumerator basta lançar o valor de retorno de getValues para um não genérico IEnumerable. Mas não posso realmente decidir se essas abordagens parecem desleixadas ou perfeitamente aceitáveis.

De qualquer forma, alguém tem uma idéia melhor de como fazer isso? Eu ouvi falar do .Synchronized métodos, mas eles parecem estar disponíveis apenas para coleções não genéricas no System.Collections espaço para nome. Talvez exista uma variante genérica disso que já existe no .NET que eu simplesmente não conheço?

Foi útil?

Solução

A maioria das coleções especifica que você não pode adicionar ou remover coisas da coleção enquanto itera. Segue-se que, para uma coleção segura por threads, você deseja bloquear outros threads de modificar a coleção enquanto qualquer thread está iterar. Isso deve ser fácil de fazer com a sintaxe dos iteradores e não exige que você faça uma cópia:

public IEnumerator<T> GetEnumerator()
{
    lock (this.Values) // or an internal mutex you use for synchronization
    {
        foreach (T val in this.Values)
        {
            yield return val;
        }
    }
    yield break;
}

Isso pressupõe que todas as outras operações que possam modificar a coleção também trava this.Values. Enquanto isso for verdade, você deve ficar bem.

Outras dicas

Infelizmente, essa implementação parece funcionar apenas para o iEnumerable.

Parece estranho para mim. Você implementou explicitamente ienumerable não genérico? Ou seja, você escreveu

public IEnumerator<T> GetEnumerator() { ...}
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator<T>(); }

Além disso, você já tentou implementar o IENUMERABLE com a sintaxe dos iteradores. Deve ser fácil:

public IEnumerator<T> GetEnumerator()
{
    T[] values;
    lock(this.Values)
        values = this.Values.ToArray();
    foreach(var value in values)
        yield return value;
}

Se você tentou, por que não atende às suas necessidades?

O tipo T[] tem uma verdade especial para o tipo System.Array do qual deriva. Matrizes como T[] foram feitos antes que os genéricos fossem introduzidos no .NET (caso contrário, a sintaxe poderia ter sido Array<T>).

O tipo System.Array tem um GetEnumerator() método de instância, public. Este método retorna não genérico IEnumerator (Desde .NET 1). E System.Array não tem implementações de interface explícitas para GetEnumerator(). Isso explica suas observações.

Mas quando os genéricos foram introduzidos no .NET 2.0, um "hack" especial foi feito para ter um T[] implemento IList<T> e suas interfaces básicas (das quais IEnumerable<T> é um). Por esse motivo, acho que é perfeitamente razoável usar:

((IList<T>)(this.getValues())).GetEnumerator()

Na versão do .NET, onde estou verificando isso, existem as seguintes classes:

namespace System
{
  public abstract class Array
  {
    private sealed class SZArrayEnumerator  // instance of this is returned with standard GetEnumerator() on a T[]
    {
    }
  }

  internal sealed class SZArrayHelper
  {
    private sealed class SZGenericArrayEnumerator<T>  // instance of this is returned with generic GetEnumerator() on a T[] which has been cast to IList<T>
    {
    }
  }
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top