У меня есть хэш много вещей ... и я сохраняю хэш как своего рода контент-идентичность. Я использую эти вещи повсюду. Хеши - это 20-байтовые массивы, и я недавно сменил их на (по-видимому) простой unsafe struct
в проекте C#, который имеет метод ToString()
. Однако во время выполнения визуализация всегда значение по умолчанию (все ноль) - даже после изменения содержимого.Почему визуализатор VS 2015 не работает над этой небезопасной структурой?
Единственные данные экземпляра в структуре - это фиксированный массив байтов, который записывается несколькими способами. Без метода ToString()
визуализатор показал некоторое слабое представление значения - но это был (я думаю) адрес фиксированного массива.
Ни один из методов перестановки не приводит к тому, что визуализатор изменяется от визуализации по умолчанию.
Например:
Даже если метод ToString() производит следующие действия:
... что ожидаемое значение (и ожидаемой визуализации).
Я пробовал [DebuggerDisplay("{ToString()}")]
и делал это сериализуемым, но все же получал те же результаты. Итак, значит ли это, что мне не повезло с небезопасными структурами, или я делаю что-то неправильно, что я не определил?
Edit:
Мои извинения за то, не вкладывая в полной, поддающейся проверке образца. Вот полное консольное приложение, которое демонстрирует проблему. Просто замените содержимое 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);
}
}
}
Установите контрольную точку ближе к концу основного метода, и пусть ваш курсор парить над любой из hash1 через hash5 переменных после того, как они были созданы.
Примечание: Вы должны установить позволяют небезопасный код в свойствах проекта.
Возможно связанные - http://stackoverflow.com/questions/34138112/a-pointer-type-static-fields-value-is-displayed-as-zero-0x0-by-the-debugger -whi – stuartd
Согласен - было бы здорово, если бы был ответ ;-) Я видел его и все равно писал, что было достаточно различий, и ни один из комментариев в этом вопросе не принес мне ничего полезного. – Clay
@ Клей, можете ли вы поделиться полным образцом, используя один диск? Поэтому я мог бы отлаживать его на моей стороне, используя тот же образец. –