Почему визуализатор отладки VS 2015 не работает с этой небезопасной структурой?

Мне нужно хешировать много вещей... и я храню хеш как своего рода идентичность контента. Я использую эти вещи везде. Хэши представляют собой 20-байтовые массивы, и я недавно изменил их на (на первый взгляд) простой unsafe struct в проекте С#, который имеет метод ToString(). Однако во время выполнения визуализация всегда имеет значение по умолчанию (все нули) — даже после изменения содержимого.

Единственным экземпляром данных в структуре является фиксированный массив байтов, запись в который осуществляется рядом методов. Без метода ToString() визуализатор показывал какое-то хромающее представление значения — но это был (думаю) адрес фиксированного массива.

Ни один из методов перестановки не приводит к изменению визуализации визуализатора по умолчанию.

Например:

визуализация

Несмотря на то, что метод ToString() выдает следующее:

визуализация2

... что является ожидаемым значением (и ожидаемой визуализацией).

Я пробовал [DebuggerDisplay( "{ToString()}" )] и сделал это сериализуемым, но все равно получаю те же результаты. Значит ли это, что мне не повезло с небезопасными структурами, или я делаю что-то не так, чего не заметил?

Изменить:

Приношу свои извинения за то, что не предоставил полный, поддающийся проверке образец. Вот полное консольное приложение, демонстрирующее проблему. Просто замените содержимое Class1.cs этим кодом, и вы увидите проблему.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;

namespace ConsoleApplication1
{
  class Program
  {
    static void Main( string[ ] args )
    {
      // sample data...
      var bytes = new byte[ 20 ] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };

      var hash1 = Hash.FromBytes( bytes );  //deserailize a hash from array
      Debug.WriteLine( hash1 );

      var hash2 = new Hash( bytes, 0, 20 );  //computes a hash over array
      Debug.WriteLine( hash2 );

      using ( var stream = new MemoryStream( bytes ) )
      {
        var hash3 = new Hash( );// empty hash
        hash3.Read( stream );  // deserialize a hash from stream
        Debug.WriteLine( hash3 );

        stream.Position = 0;

        var hash4 = new Hash( stream ); //compute hash over stream
        Debug.WriteLine( hash4 );

        var hash5 = new Hash( "Compute the hash of a string" );
        Debug.WriteLine( hash5 );

        Debug.Assert( hash1 == hash3, "Oops!" );
        Debug.Assert( hash2 == hash4, "Nope!" );
        Debug.Assert( hash1 != hash2, "Golly!" );
        Debug.Assert( hash3 != hash4, "Shucks!" );

      }
    }
  }


  /// <summary>Represents a hash of a string or byte array</summary>
  [StructLayout( LayoutKind.Sequential )]
  public unsafe struct Hash: IComparable<Hash>
  {

    #region statics and constants
    /// <summary>Character map for byte array to string</summary>
    readonly static char[ ] hex = new char[ ] {
              '0', '1', '2', '3',
              '4', '5', '6', '7',
              '8', '9', 'a', 'b',
              'c', 'd', 'e', 'f' };

    /// <summary>Synchronization primitive</summary>
    readonly static object sync = new object( );

    /// <summary>Buffer for reading hashes from streams, strings, and arrays</summary>
    readonly static byte[ ] buffer = new byte[ 20 ];

    /// <summary>ToString workspace</summary>
    static char[ ] hexChars = new char[ Length * 2 ];

    /// <summary>Returns a hash that has no value</summary>
    public readonly static Hash EmptyHash = new Hash( );

    /// <summary>Retruns the length of any <see cref="Hash"/></summary>
    public const int Length = 20;


    /// <summary>Returns a <see cref="HashAlgorithm"/> that the system uses to compute hashes</summary>
    public static HashAlgorithm GetHasher( )
    {
      return new SHA1Managed( );
    }

    #endregion

    #region private data
    /// <summary>A pointer to the underlying data</summary>
    fixed byte value[ 20 ];
    #endregion

    #region construction

    /// <summary>Creates a hash from a string</summary>
    public Hash( string hashable )
    {
      fixed ( byte* bytes = value, sourceBytes = GetHasher( ).ComputeHash( Encoding.Unicode.GetBytes( hashable ) ) )
      {
        NativeMethods.CopyMemory( bytes, sourceBytes, Length );
      }
    }

    /// <summary>Creates a hash from a byte array</summary>
    public Hash( byte[ ] source, int index, int length )
    {
      fixed ( byte* bytes = value, sourceBytes = GetHasher( ).ComputeHash( source, index, length ) )
      {
        NativeMethods.CopyMemory( bytes, sourceBytes, Length );
      }
    }

    /// <summary>Creates a hash from a series of hashes</summary>
    public Hash( IEnumerable<Hash> hashes )
    {
      var hasher = GetHasher( );
      var buffer = new byte[ Length ];
      hashes.Do( key =>
      {
        key.CopyTo( buffer );
        hasher.TransformBlock( buffer, 0, Length, buffer, 0 );
      } );
      hasher.TransformFinalBlock( buffer, 0, 0 );
      fixed ( byte* bytes = value, source = hasher.Hash )
      {
        NativeMethods.CopyMemory( bytes, source, Length );
      }
    }


    /// <summary>Creates a hash over a stream from current position to end</summary>
    public Hash( Stream stream )
    {
      const int bufferSize = 4096;
      var hasher = GetHasher( );
      var bytesRead = 0;
      var buffer = new byte[ bufferSize ];
      while ( true )
      {
        bytesRead = stream.Read( buffer, 0, bufferSize );
        if ( bytesRead == 0 )
        {
          hasher.TransformFinalBlock( buffer, 0, 0 );
          break;
        }
        else
        {
          hasher.TransformBlock( buffer, 0, bytesRead, buffer, 0 );
        }
      }
      fixed ( byte* bytes = value, source = hasher.Hash )
      {
        NativeMethods.CopyMemory( bytes, source, Length );
      }
    }

    #endregion


    #region methods
    /// <summary>Copies the hash to the start of a byte array</summary>
    public void CopyTo( byte[ ] buffer )
    {
      CopyTo( buffer, 0 );
    }

    /// <summary>Copies the hash to a byte array</summary>
    public void CopyTo( byte[ ] buffer, int offset )
    {
      if ( buffer == null ) throw new ArgumentNullException( nameof( buffer ) );
      if ( buffer.Length < ( offset + Length ) ) throw new ArgumentOutOfRangeException( nameof( buffer ) );
      fixed ( byte* bytes = value, dest = buffer )
      {
        NativeMethods.CopyMemory( dest + offset, bytes, Length );
      }
    }

    /// <summary>Returns a byte-array representation of the <see cref="Hash"/></summary>
    /// <remarks>The returned value is a copy</remarks>
    public byte[ ] GetBytes( )
    {
      var results = new byte[ Length ];
      fixed ( byte* bytes = value, target = results )
      {
        NativeMethods.CopyMemory( target, bytes, Length );
      }
      return results;
    }

    /// <summary>Compares this hash to another</summary>
    public int CompareTo( Hash other )
    {
      var comparedByte = 0;
      fixed ( byte* bytes = value )
      {
        for ( int i = 0; i < Length; i++ )
        {
          comparedByte = ( *( bytes + i ) ).CompareTo( other.value[ i ] );
          if ( comparedByte != 0 ) break;
        }
        return comparedByte;
      }
    }

    /// <summary>Returns true if <paramref name="obj"/> is a <see cref="Hash"/> and it's value exactly matches</summary>
    /// <param name="obj">The <see cref="Hash"/> to compare to this one</param>
    /// <returns>true if the values match</returns>
    public override bool Equals( object obj )
    {
      if ( obj == null || !( obj is Hash ) ) return false;
      var other = ( Hash ) obj;
      return CompareTo( other ) == 0;
    }

    /// <summary>Returns a .Net hash code for this <see cref="Hash"/></summary>
    public override int GetHashCode( )
    {
      unchecked
      {
        int hashCode = 17;
        fixed ( byte* bytes = value )
        {
          for ( int i = 0; i < Length; i++ )
          {
            hashCode = hashCode * 31 + *( bytes + i );
          }
          return hashCode;
        }
      }
    }

    /// <summary>Returns a hex string representation of the hash</summary>
    public override string ToString( )
    {
      lock ( sync )
      {
        fixed ( char* hexFixed = hex, hexCharsFixed = hexChars )
        {
          fixed ( byte* bytes = value )
          {
            for ( int i = 0; i < Length; i++ )
            {
              *( hexCharsFixed + ( i * 2 ) ) = *( hexFixed + ( *( bytes + i ) >> 4 ) );
              *( hexCharsFixed + ( 1 + ( i * 2 ) ) ) = *( hexFixed + ( *( bytes + i ) & 0xf ) );
            }
            return new string( hexChars );
          }
        }
      }
    }

    /// <summary>Reads a <see cref="Hash"/> from the provided stream</summary>
    public void Read( Stream stream )
    {
      lock ( sync )
      {
        var retryCount = 0;
        var bytesRead = ReadStream( stream, buffer, 0, Length, ref retryCount );
        if ( bytesRead == Length )
        {
          fixed ( byte* bytes = value, sourceBytes = buffer )
          {
            NativeMethods.CopyMemory( bytes, sourceBytes, Length );
          }
        }
      }
    }

    /// <summary>Tries hard to populate a <see cref="Hash"/> from a stream - across multiple reads if necessary - up to a point</summary>
    int ReadStream( Stream stream, byte[ ] buffer, int offset, int length, ref int retryCount )
    {
      const int maxStreamReadRetries = 3;

      var bytesRead = stream.Read( buffer, offset, length );
      var done = bytesRead == 0 || bytesRead == length;  // eos, timeout, or success
      if ( !done )
      {
        if ( retryCount++ >= maxStreamReadRetries ) return 0;
        bytesRead += ReadStream( stream, buffer, bytesRead, length - bytesRead, ref retryCount );
      }
      return bytesRead;
    }

    /// <summary>Writes the hash to a stream</summary>
    public void Write( Stream stream )
    {
      lock ( sync )
      {
        fixed ( byte* bytes = value, targetBytes = buffer )
        {
          NativeMethods.CopyMemory( targetBytes, bytes, Length );
        }
        stream.Write( buffer, 0, Length );
      }
    }

    /// <summary>Returns true if the hash has no value</summary>
    public bool IsEmpty( )
    {
      return Equals( EmptyHash );
    }

    /// <summary>Returns the result of XORing two <see cref="Hash"/>es</summary>
    public static Hash Combine( Hash a, Hash b )
    {
      var results = new Hash( );
      for ( int i = 0; i < Length; i++ )
      {
        *( results.value + i ) = ( byte ) ( *( a.value + i ) ^ *( b.value + i ) );
      }
      return results;
    }

    /// <summary>Returns the first non-empty hash from a list</summary>
    public static Hash FirstNotEmpty( params Hash[ ] hashes )
    {
      foreach ( var hash in hashes ) if ( !hash.IsEmpty( ) ) return hash;
      throw new ArgumentOutOfRangeException( nameof( hashes ) );
    }

    /// <summary>Implements == operator</summary>
    public static bool operator ==( Hash a, Hash b )
    {
      return a.Equals( b );
    }

    /// <summary>Implements != operator</summary>
    public static bool operator !=( Hash a, Hash b )
    {
      return !a.Equals( b );
    }


    /// <summary>Converts a byte array to a <see cref="Hash"/></summary>
    public static Hash FromBytes( byte[ ] hashBytes, int offset = 0 )
    {
      if ( hashBytes == null ) throw new ArgumentNullException( nameof( hashBytes ) );
      if ( ( hashBytes.Length + offset ) < Length ) throw new ArgumentOutOfRangeException( nameof( hashBytes ) );
      var hash = new Hash( );
      fixed ( byte* sourceBytes = hashBytes )
        NativeMethods.CopyMemory( hash.value, sourceBytes + offset, Length );
      return hash;
    }
    #endregion

  }

  class NativeMethods
  {
    [DllImport( "Kernel32", SetLastError = true, EntryPoint = "CopyMemory" )]
    internal unsafe static extern void CopyMemory( void* destination, void* source, uint length );
  }


  static class Extensions
  {
    /// <summary>Applies action to each element of the collection.</summary>
    public static void Do<T>( this IEnumerable<T> enumerable, Action<T> action )
    {
      if ( enumerable == null ) throw new ArgumentNullException( "enumerable" );
      if ( action == null ) throw new ArgumentNullException( "action" );
      foreach ( var item in enumerable ) action( item );
    }
  }
}

Установите точку останова в конце основного метода и наведите курсор на любую из переменных от хэш1 до хэш5 после их создания.

Примечание. В свойствах проекта необходимо установить разрешить небезопасный код.


person Clay    schedule 08.12.2016    source источник
comment
Возможно, связано - stackoverflow.com/questions/34138112/   -  person stuartd    schedule 08.12.2016
comment
Согласен - было бы круто, если бы был ответ ;-) Я видел это и все равно написал об этом, поскольку различий было достаточно, и ни один из комментариев в этом вопросе не принес мне никакой пользы.   -  person Clay    schedule 08.12.2016
comment
@Clay, можете ли вы поделиться полным образцом с одного диска? Так что я мог отладить его на своей стороне, используя тот же образец.   -  person Jack Zhai-MSFT    schedule 09.12.2016
comment
@JackZhai-MSFT, я был плохим мальчиком и не привел поддающийся проверке пример. Я отредактировал вопрос, чтобы включить его. Спасибо!   -  person Clay    schedule 09.12.2016
comment
@JackZhai-MSFT, ты можешь воспроизвести?   -  person Clay    schedule 12.12.2016
comment
@Clay, Инструменты->Параметры->Отладка->Общие->Включить использование управляемого режима совместимости, как насчет результата?   -  person Jack Zhai-MSFT    schedule 13.12.2016
comment
Идеально! Отправьте как ответ, и я приму!   -  person Clay    schedule 13.12.2016
comment
Выкладываю ответ, Клей :) Хорошего дня!   -  person Jack Zhai-MSFT    schedule 15.12.2016


Ответы (1)


В разделе Инструменты->Параметры->Отладка->Общие->Включить «Использовать управляемый режим совместимости». Это будет хорошо работать.

введите здесь описание изображения

person Jack Zhai-MSFT    schedule 15.12.2016
comment
Большое спасибо! Очень приятно видеть эти значения. - person Clay; 15.12.2016
comment
+1, решает и мою проблему. Вы можете указать тот же ответ здесь: the-debugger-whi">ссылка - person Oguz Ozgul; 16.01.2017