Pergunta

Existe uma maneira de obter um identificador único de uma instância?

GetHashCode() é o mesmo para as duas referências que apontam para a mesma instância. No entanto, duas instâncias diferentes podem (facilmente) obter o mesmo código hash:

Hashtable hashCodesSeen = new Hashtable();
LinkedList<object> l = new LinkedList<object>();
int n = 0;
while (true)
{
    object o = new object();
    // Remember objects so that they don't get collected.
    // This does not make any difference though :(
    l.AddFirst(o);
    int hashCode = o.GetHashCode();
    n++;
    if (hashCodesSeen.ContainsKey(hashCode))
    {
        // Same hashCode seen twice for DIFFERENT objects (n is as low as 5322).
        Console.WriteLine("Hashcode seen twice: " + n + " (" + hashCode + ")");
        break;
    }
    hashCodesSeen.Add(hashCode, null);
}

Eu estou escrevendo um suplemento de depuração, e eu preciso para obter algum tipo de ID para uma referência que é exclusivo durante a execução do programa.

Já conseguiu endereço interno da instância, que é único até que o coletor de lixo (GC) compacta o heap (= move os objetos = muda os endereços).

pergunta Stack Overflow padrão de implementação para Object.GetHashCode () pode ser parente.

Os objetos não estão sob meu controle como eu estou acessando objetos em um programa que está sendo depurado usando a API do depurador. Se eu estava no controle dos objetos, acrescentando meus próprios identificadores únicos seria trivial.

Eu queria que o ID único para a construção de um ID hashtable -> objeto, para ser capaz de pesquisar objetos já vi. Por agora eu resolvido assim:

Build a hashtable: 'hashCode' -> (list of objects with hash code == 'hashCode')
Find if object seen(o) {
    candidates = hashtable[o.GetHashCode()] // Objects with the same hashCode.
    If no candidates, the object is new
    If some candidates, compare their addresses to o.Address
        If no address is equal (the hash code was just a coincidence) -> o is new
        If some address equal, o already seen
}
Foi útil?

Solução

A referência é o identificador exclusivo para o objeto. Eu não sei de nenhuma maneira de converter isso em algo como uma corda etc. O valor da referência vai mudar durante a compactação (como você viu), mas cada valor Um anterior será alterado para o valor B, de modo muito como código seguro está em causa ainda é uma identificação única.

Se os objetos envolvidos estão sob seu controle, você pode criar um mapeamento usando fraca referências (para evitar impedindo a coleta de lixo) a partir de uma referência a um ID de sua escolha (GUID, inteiro, qualquer que seja). Que gostaria de acrescentar uma certa quantidade de sobrecarga e complexidade, no entanto.

Outras dicas

.NET 4 e posterior

Boa notícia, todos!

A ferramenta perfeita para este trabalho é construído em .NET 4 e é chamado de ConditionalWeakTable<TKey, TValue> . Esta classe:

  • pode ser usado para dados arbitrários associados com instâncias de objetos gerenciados muito parecido com um dicionário (embora é não um dicionário)
  • não depende de endereços de memória, então é imune ao GC compactação do heap
  • não manter objetos vivos só porque eles foram inseridos como chaves em cima da mesa, para que ele possa ser usado sem fazer cada objeto em seu processo de viver para sempre
  • usa igualdade de referência para determinar a identidade do objeto; moveover, autores de classe não podem modificar esse comportamento para que ele possa ser usado consistentemente em objetos de qualquer tipo
  • pode ser preenchido on the fly, por isso não requer que você injetar código dentro construtores de objetos

Você pode desenvolver sua própria coisa em um segundo. Por exemplo:

   class Program
    {
        static void Main(string[] args)
        {
            var a = new object();
            var b = new object();
            Console.WriteLine("", a.GetId(), b.GetId());
        }
    }

    public static class MyExtensions
    {
        //this dictionary should use weak key references
        static Dictionary<object, int> d = new Dictionary<object,int>();
        static int gid = 0;

        public static int GetId(this object o)
        {
            if (d.ContainsKey(o)) return d[o];
            return d[o] = gid++;
        }
    }   

Você pode escolher o que você gostaria de ter como identificação única em seu próprio país, por exemplo, System.Guid.NewGuid () ou simplesmente inteiro para acesso mais rápido.

Como sobre este método:

Definir um campo no primeiro objeto para um novo valor. Se o mesmo campo no segundo objeto tem o mesmo valor, é provavelmente a mesma instância. Caso contrário, sair como diferentes.

Agora, defina o campo no primeiro objeto para um novo valor diferente. Se o mesmo campo no segundo objeto foi alterado para o valor diferente, é, definitivamente, a mesma instância.

Não se esqueça de campo set na primeira volta objeto para seu valor original na saída.

Problemas?

É possível fazer um único identificador de objetos no Visual Studio:. Na janela do relógio, clique com o botão direito do mouse na variável de objeto e escolha ID Tornar objeto no menu de contexto

Infelizmente, esta é uma etapa manual, e eu não acredito que o identificador pode ser acessado por meio de código.

Você teria que atribuir um identificador Faça você mesmo, manualmente -., Quer dentro da instância, ou externamente

Para registros relacionados a um banco de dados, a chave primária pode ser útil (mas você ainda pode obter duplicatas). Como alternativa, use um Guid, ou manter seu próprio contador, alocando usando Interlocked.Increment (e torná-lo grande o suficiente que não é provável que overflow).

Eu sei que este foi respondida, mas é pelo menos interessante notar que você pode usar:

http://msdn.microsoft.com/en- us / library / system.object.referenceequals.aspx

O que não vai lhe dar um "ID único" diretamente, mas combinado com WeakReferences (e um hashset?) Poderia dar-lhe uma maneira muito fácil de rastrear várias instâncias.

As informações dou aqui não é novo, eu só adicionado este para ser completo.

A idéia deste código é bastante simples:

  • Objetos precisa de um ID único, que não está lá por padrão. Em vez disso, temos de contar com a próxima melhor coisa, que é RuntimeHelpers.GetHashCode para obter uma espécie de identificação única
  • Para verificar singularidade, isto implica que precisamos usar object.ReferenceEquals
  • No entanto, nós ainda gostaria de ter uma identificação única, então eu adicionei um GUID, que por definição é único.
  • Porque eu não gosto de bloqueio tudo se eu não tiver que, eu não uso ConditionalWeakTable.

Combinada, que lhe dará o seguinte código:

public class UniqueIdMapper
{
    private class ObjectEqualityComparer : IEqualityComparer<object>
    {
        public bool Equals(object x, object y)
        {
            return object.ReferenceEquals(x, y);
        }

        public int GetHashCode(object obj)
        {
            return RuntimeHelpers.GetHashCode(obj);
        }
    }

    private Dictionary<object, Guid> dict = new Dictionary<object, Guid>(new ObjectEqualityComparer());
    public Guid GetUniqueId(object o)
    {
        Guid id;
        if (!dict.TryGetValue(o, out id))
        {
            id = Guid.NewGuid();
            dict.Add(o, id);
        }
        return id;
    }
}

Para usá-lo, crie uma instância da UniqueIdMapper e usar o GUID é ele retorna para os objetos.


Adenda

Assim, há um pouco mais acontecendo aqui; deixe-me escrever um baixo pouco sobre ConditionalWeakTable.

ConditionalWeakTable faz um par de coisas. A coisa mais importante é que ele doens't se preocupam com o coletor de lixo, isto é: os objetos que fazem referência nesta tabela serão recolhidos independentemente. Se você pesquisar um objeto, ele funciona basicamente o mesmo que o dicionário acima.

Não Curioso? Afinal, quando um objeto está sendo recolhida pela GC, ele verifica se existem referências ao objeto, e se houver, que recolhe-los. Portanto, se há um objeto do ConditionalWeakTable, por que o objeto referenciado ser recolhida, então?

ConditionalWeakTable usa um pequeno truque, que algumas outras estruturas .NET também usam: em vez de armazenar uma referência para o objeto, ele realmente armazena um IntPtr. Porque não é uma referência real, o objeto pode ser coletado.

Assim, neste momento, existem 2 problemas para endereço. Primeiro, os objetos podem ser movidos na pilha, de modo que vamos usar como IntPtr? E segundo, como sabemos que os objetos têm uma referência ativa?

  • O objeto pode ser fixado na pilha, e seu ponteiro real pode ser armazenado. Quando o GC atinge o objeto para a remoção, ele unpins-lo e recolhe-lo. No entanto, isso significaria que temos um recurso preso, que não é uma boa idéia se você tem um monte de objetos (devido a problemas de fragmentação de memória). Isto provavelmente não é como ele funciona.
  • Quando o GC move um objeto, ele chama de volta, o que pode, em seguida, atualizar as referências. Isso pode ser como ele é implementado a julgar pelas chamadas externas em DependentHandle -. Mas eu acredito que é um pouco mais sofisticado
  • Não é o ponteiro para o objeto em si, mas um ponteiro na lista de todos os objetos do GC é armazenado. O IntPtr ou é um índice ou um ponteiro nesta lista. A lista só muda quando um objeto muda gerações, altura em que um simples retorno de chamada pode atualizar os ponteiros. Se você se lembrar de como Mark & ??trabalhos varredura, isso faz mais sentido. Não há nenhuma fixação e remoção é como era antes. Eu acredito que esta é a forma como ele funciona em DependentHandle.

Esta última solução exige que o tempo de execução não voltar a usar a lista baldes até que sejam explicitamente libertado, e também exige que todos os objetos são recuperados por uma chamada para o tempo de execução.

Se assumirmos que eles usam esta solução, podemos também abordar o segundo problema. O algoritmo Mark & ??varredura mantém informado dos quais foram coletadas objetos; assim que tiver sido coletado, sabemos neste momento. Depois que o objeto verifica se o objeto está lá, ele chama 'Free', o que remove o ponteiro e a entrada da lista. O objeto é realmente ido.

Uma coisa importante a nota neste ponto é que as coisas vão muito mal se ConditionalWeakTable é atualizado em vários segmentos e não se é thread-safe. O resultado seria um vazamento de memória. É por isso que todas as chamadas em ConditionalWeakTable fazer um simples 'lock' que garante que isso não aconteça.

Outra coisa a notar é que a limpeza de entradas tem que acontecer uma vez em um while. Enquanto os objectos reais vai ser limpo pelo GC, as entradas não são. É por isso que ConditionalWeakTable só cresce em tamanho. Uma vez que atinge um certo limite (determinado por acaso colisão no hash), ele aciona um Resize, que verifica se os objetos têm de ser limpo -. Se o fizerem, free é chamado no processo de GC, removendo a alça IntPtr

Eu acredito que esta é também por isso que DependentHandle não está exposta diretamente - você não quer mexer com as coisas e obter um vazamento de memória como resultado. A próxima melhor coisa para isso é um WeakReference (que também armazena uma IntPtr em vez de um objeto.) - mas que, infelizmente, não incluem o aspecto 'dependência'

O que resta é para você para cerca de brinquedo com a mecânica, de modo que você pode ver a dependência em ação. Certifique-se de iniciá-lo várias vezes e ver os resultados:

class DependentObject
{
    public class MyKey : IDisposable
    {
        public MyKey(bool iskey)
        {
            this.iskey = iskey;
        }

        private bool disposed = false;
        private bool iskey;

        public void Dispose()
        {
            if (!disposed)
            {
                disposed = true;
                Console.WriteLine("Cleanup {0}", iskey);
            }
        }

        ~MyKey()
        {
            Dispose();
        }
    }

    static void Main(string[] args)
    {
        var dep = new MyKey(true); // also try passing this to cwt.Add

        ConditionalWeakTable<MyKey, MyKey> cwt = new ConditionalWeakTable<MyKey, MyKey>();
        cwt.Add(new MyKey(true), dep); // try doing this 5 times f.ex.

        GC.Collect(GC.MaxGeneration);
        GC.WaitForFullGCComplete();

        Console.WriteLine("Wait");
        Console.ReadLine(); // Put a breakpoint here and inspect cwt to see that the IntPtr is still there
    }

Se você estiver escrevendo um módulo em seu próprio código para um uso específico, método de majkinetor PODER ter funcionado. Mas há alguns problemas.

Primeiro , o documento oficial faz não garantia de que os GetHashCode() retorna um identificador único (ver Método Object.GetHashCode () ):

Você não deve presumir que os códigos de hash igual implica igualdade do objeto.

Segunda , suponha que você tem uma quantidade muito pequena de objetos de modo que GetHashCode() irá funcionar na maioria dos casos, este método pode ser substituído por alguns tipos.
Por exemplo, você estiver usando alguma classe C e substitui GetHashCode() a retornar sempre 0. Em seguida, todos os objetos de C terá o mesmo código hash. Infelizmente, Dictionary, HashTable e alguns outros recipientes associativas vai fazer usar esse método:

Um código hash é um valor numérico que é usada para inserir e identificar um objecto de uma recolha à base de hash, tais como o dicionário classe, a classe de tabela de hash, ou um tipo derivado da classe DictionaryBase. O método GetHashCode fornece esse código de hash para algoritmos que precisam verificações rápidas de igualdade do objeto.

Assim, esta abordagem tem grandes limitações.

e ainda mais , e se você quiser construir uma biblioteca de propósito geral? Não só não você é capaz de modificar o código fonte das classes usados, mas o seu comportamento também é imprevisível.

Eu aprecio isso Jon e Simon colocaram suas respostas, e eu vou postar um exemplo de código e uma sugestão sobre o desempenho abaixo.

using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Collections.Generic;


namespace ObjectSet
{
    public interface IObjectSet
    {
        /// <summary> check the existence of an object. </summary>
        /// <returns> true if object is exist, false otherwise. </returns>
        bool IsExist(object obj);

        /// <summary> if the object is not in the set, add it in. else do nothing. </summary>
        /// <returns> true if successfully added, false otherwise. </returns>
        bool Add(object obj);
    }

    public sealed class ObjectSetUsingConditionalWeakTable : IObjectSet
    {
        /// <summary> unit test on object set. </summary>
        internal static void Main() {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            ObjectSetUsingConditionalWeakTable objSet = new ObjectSetUsingConditionalWeakTable();
            for (int i = 0; i < 10000000; ++i) {
                object obj = new object();
                if (objSet.IsExist(obj)) { Console.WriteLine("bug!!!"); }
                if (!objSet.Add(obj)) { Console.WriteLine("bug!!!"); }
                if (!objSet.IsExist(obj)) { Console.WriteLine("bug!!!"); }
            }
            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);
        }


        public bool IsExist(object obj) {
            return objectSet.TryGetValue(obj, out tryGetValue_out0);
        }

        public bool Add(object obj) {
            if (IsExist(obj)) {
                return false;
            } else {
                objectSet.Add(obj, null);
                return true;
            }
        }

        /// <summary> internal representation of the set. (only use the key) </summary>
        private ConditionalWeakTable<object, object> objectSet = new ConditionalWeakTable<object, object>();

        /// <summary> used to fill the out parameter of ConditionalWeakTable.TryGetValue(). </summary>
        private static object tryGetValue_out0 = null;
    }

    [Obsolete("It will crash if there are too many objects and ObjectSetUsingConditionalWeakTable get a better performance.")]
    public sealed class ObjectSetUsingObjectIDGenerator : IObjectSet
    {
        /// <summary> unit test on object set. </summary>
        internal static void Main() {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            ObjectSetUsingObjectIDGenerator objSet = new ObjectSetUsingObjectIDGenerator();
            for (int i = 0; i < 10000000; ++i) {
                object obj = new object();
                if (objSet.IsExist(obj)) { Console.WriteLine("bug!!!"); }
                if (!objSet.Add(obj)) { Console.WriteLine("bug!!!"); }
                if (!objSet.IsExist(obj)) { Console.WriteLine("bug!!!"); }
            }
            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);
        }


        public bool IsExist(object obj) {
            bool firstTime;
            idGenerator.HasId(obj, out firstTime);
            return !firstTime;
        }

        public bool Add(object obj) {
            bool firstTime;
            idGenerator.GetId(obj, out firstTime);
            return firstTime;
        }


        /// <summary> internal representation of the set. </summary>
        private ObjectIDGenerator idGenerator = new ObjectIDGenerator();
    }
}

No meu teste, o ObjectIDGenerator irá lançar uma exceção para reclamar que existem muitos objetos ao criar 10.000.000 objetos (10x do que no código acima) no circuito for.

Além disso, o resultado do benchmark é que a implementação ConditionalWeakTable é 1,8x mais rápido do que a implementação ObjectIDGenerator.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top