//******************************************************************************************************
// FileBackedLookupTableBase.cs - Gbtc
//
// Copyright © 2014, Grid Protection Alliance. All Rights Reserved.
//
// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
// the NOTICE file distributed with this work for additional information regarding copyright ownership.
// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may
// not use this file except in compliance with the License. You may obtain a copy of the License at:
//
// http://www.opensource.org/licenses/MIT
//
// Unless agreed to in writing, the subject software distributed under the License is distributed on an
// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
// License for the specific language governing permissions and limitations.
//
// Code Modification History:
// ----------------------------------------------------------------------------------------------------
// 12/02/2014 - Stephen C. Wills
// Generated original version of source code.
//
//******************************************************************************************************
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
using GSF.IO;
using GSF.IO.Checksums;
namespace GSF.Collections
{
internal enum LookupTableType
{
///
/// Dictionary with keys and values in the item nodes.
///
Dictionary,
///
/// HashSet with lookup node markers for set operations.
///
HashSet
}
internal sealed class FileBackedLookupTable : IEnumerable>, IDisposable
{
#region [ Members ]
// Nested Types
private class HeaderNode
{
public const int SignatureSize = 16;
public const int FixedSize = SignatureSize + 4 * sizeof(long);
public HeaderNode(LookupTableType type)
{
if (type == LookupTableType.Dictionary)
Signature = Guid.Parse(DictionarySignature).ToRfcBytes();
else
Signature = Guid.Parse(HashSetSignature).ToRfcBytes();
}
public byte[] Signature;
public long Count;
public long Capacity;
public long ItemSectionPointer;
public long EndOfFilePointer;
}
private class JournalNode
{
public const int FixedSize = sizeof(int) + 3 * sizeof(long) + sizeof(int);
public const int None = 0;
public const int Set = 1;
public const int Delete = 2;
public const int GrowLookupSection = 3;
public const int RebuildLookupTable = 4;
public const int WriteItemNodePointers = 5;
public const int Truncate = 6;
public const int Clear = 7;
public int Operation;
public long LookupPointer;
public long ItemPointer;
public long Sync;
public int Checksum;
}
private class LookupNode
{
public long ItemPointer;
public int Marker;
}
private class ItemNode
{
// Size of only the fixed size portion of
// the item node (not including key and value)
public const int FixedSize = 2 * sizeof(long) + sizeof(int);
public long LookupPointer;
public long NextItemPointer;
public int HashCode;
public TKey Key;
public TValue Value;
}
private class CRCStream : Stream
{
#region [ Members ]
// Fields
private Crc32 m_checksum = new Crc32();
#endregion
#region [ Properties ]
public override bool CanRead => false;
public override bool CanSeek => false;
public override bool CanWrite => true;
public uint Value => m_checksum.Value;
#endregion
#region [ Methods ]
public override void Flush() { }
public void Reset() =>
m_checksum.Reset();
public override void Write(byte[] buffer, int offset, int count) =>
m_checksum.Update(buffer, offset, count);
#endregion
#region [ Not Supported ]
public override long Length
{
get
{
throw new NotSupportedException();
}
}
public override long Position
{
get
{
throw new NotSupportedException();
}
set
{
throw new NotSupportedException();
}
}
public override int Read(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
#endregion
}
// The default equality comparer for strings does not guarantee
// consistency across platforms or versions of the CLR. This comparer
// uses CRC-32 for its hashing function to provide that guarantee.
private class DefaultKeyComparer : IEqualityComparer
{
public bool Equals(TKey x, TKey y)
{
using (MemoryStream xStream = new MemoryStream())
using (MemoryStream yStream = new MemoryStream())
{
WriteKeyAction(xStream, x);
WriteKeyAction(yStream, y);
if (xStream.Length != yStream.Length)
return false;
byte[] xBuffer = xStream.GetBuffer();
byte[] yBuffer = yStream.GetBuffer();
for (int i = 0; i < xStream.Length; i++)
{
if (xBuffer[i] != yBuffer[i])
return false;
}
return true;
}
}
public int GetHashCode(TKey obj)
{
using (CRCStream crcStream = new CRCStream())
{
WriteKeyAction(crcStream, obj);
return unchecked((int)crcStream.Value);
}
}
public static readonly DefaultKeyComparer Default = new DefaultKeyComparer();
}
private class KeysEnumerable : IEnumerable
{
private Func> m_getEnumeratorFunc;
public KeysEnumerable(Func> getEnumeratorFunc)
{
m_getEnumeratorFunc = getEnumeratorFunc;
}
public IEnumerator GetEnumerator()
{
return m_getEnumeratorFunc();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
// Constants
public const string DictionarySignature = "3165E4F9-203B-4741-A186-EA34659A94B7";
public const string HashSetSignature = "6527713F-78AE-43DA-8E37-718AFED99927";
private const double MaximumLoadFactor = 0.7D;
private const int MaximumChainedEmptyNodes = 3;
// Fields
private HeaderNode m_headerNode;
private JournalNode m_journalNode;
private CachedFileStream m_fileStream;
private BinaryWriter m_fileWriter;
private BinaryReader m_fileReader;
private string m_filePath;
private IEqualityComparer m_keyComparer;
private int m_fragmentationCount;
private LookupTableType m_lookupTableType;
#endregion
#region [ Constructors ]
///
/// Creates a new instance of the class.
///
/// Type of the lookup table used to tweak the file format.
/// Either or cannot be serialized.
public FileBackedLookupTable(LookupTableType lookupTableType)
: this(lookupTableType, (IEqualityComparer)null)
{
}
///
/// Creates a new instance of the class.
///
/// Type of the lookup table used to tweak the file format.
/// The path to the file used to store the lookup table.
/// is a zero-length string, contains only white space, or contains one or more invalid characters as defined by .
/// is null.
/// Either or cannot be serialized.
public FileBackedLookupTable(LookupTableType lookupTableType, string filePath)
: this(lookupTableType, filePath, null)
{
}
///
/// Creates a new instance of the class.
///
/// Type of the lookup table used to tweak the file format.
/// The equality comparer used to compare keys in the lookup table.
/// Either or cannot be serialized.
public FileBackedLookupTable(LookupTableType lookupTableType, IEqualityComparer keyComparer)
: this(lookupTableType, Path.GetTempFileName(), keyComparer)
{
}
///
/// Creates a new instance of the class.
///
/// Type of the lookup table used to tweak the file format.
/// The path to the file used to store the lookup table.
/// The equality comparer used to compare keys in the lookup table.
/// is a zero-length string, contains only white space, or contains one or more invalid characters as defined by .
/// is null.
/// Either or cannot be serialized.
public FileBackedLookupTable(LookupTableType lookupTableType, string filePath, IEqualityComparer keyComparer)
{
if ((object)WriteKeyAction == null || (object)ReadKeyFunc == null)
throw new InvalidOperationException("Unable to create FileBackedLookupTable with keys that are not serializable");
if (lookupTableType == LookupTableType.Dictionary && ((object)WriteValueAction == null || (object)ReadValueFunc == null))
throw new InvalidOperationException("Unable to create FileBackedLookupTable with values that are not serializable");
if ((object)filePath == null)
throw new ArgumentNullException(nameof(filePath));
if (string.IsNullOrWhiteSpace(filePath))
throw new ArgumentException("Path is zero-length or contains only whitespace", nameof(filePath));
m_lookupTableType = lookupTableType;
FilePath = filePath;
if ((object)keyComparer != null)
m_keyComparer = keyComparer;
else
m_keyComparer = DefaultKeyComparer.Default;
}
#endregion
#region [ Properties ]
///
/// Gets or sets the path to the file backing this lookup table.
///
/// FilePath contains one or more invalid characters as defined by .
///
/// Changes to this property will cause the file to close if the file is already opened.
/// Data will not be automatically written from the old file to the new file.
///
public string FilePath
{
get
{
return m_filePath;
}
set
{
char[] invalidPathChars;
if (m_filePath != value)
{
if (!string.IsNullOrWhiteSpace(value))
{
invalidPathChars = Path.GetInvalidPathChars();
if (value.Any(invalidPathChars.Contains))
throw new ArgumentException($"Path contains one or more invalid characters: {value}", nameof(value));
}
Close();
m_filePath = value;
}
}
}
///
/// Gets the number of elements contained in the .
///
///
/// The number of elements contained in the .
///
public int Count
{
get
{
if ((object)m_fileStream == null)
OpenImplicit();
return (int)m_headerNode.Count;
}
}
///
/// Gets a value indicating whether the is read-only.
///
///
/// true if the is read-only; otherwise, false.
///
public bool IsReadOnly
{
get
{
if ((object)m_fileStream == null)
OpenImplicit();
return (object)m_fileWriter == null;
}
}
///
/// Gets or sets the signature of the file backing the lookup table.
///
/// Attempt is made to set Signature to a null value.
/// Attempt is made to set Signature to a value larger than the maximum allowed size.
/// Attempt is made to modify Signature of a read-only lookup table.
public byte[] Signature
{
get
{
byte[] signature;
if ((object)m_fileStream == null)
OpenImplicit();
signature = new byte[HeaderNode.SignatureSize];
Buffer.BlockCopy(m_headerNode.Signature, 0, signature, 0, HeaderNode.SignatureSize);
return signature;
}
set
{
if ((object)value == null)
throw new ArgumentNullException(nameof(value));
if (value.Length > HeaderNode.SignatureSize)
throw new ArgumentException("Attempt was made to set signature to a value larger than the maximum signature size of " + HeaderNode.SignatureSize);
FailIfReadOnly();
Buffer.BlockCopy(value, 0, m_headerNode.Signature, 0, value.Length);
Array.Clear(m_headerNode.Signature, value.Length, HeaderNode.SignatureSize - value.Length);
Write(m_headerNode);
}
}
///
/// Gets or sets the element with the specified key.
///
///
/// The element with the specified key.
///
/// The key of the element to get or set.
/// is null.
/// The property is retrieved and is not found.
/// The property is set and the is read-only.
public TValue this[TKey key]
{
get
{
TValue value;
if ((object)key == null)
throw new ArgumentNullException(nameof(key));
if (!TryGetValue(key, out value))
throw new KeyNotFoundException($"Item with the given key ({key}) was not found in the lookup table: {m_filePath}");
return value;
}
set
{
long lookupPointer;
long itemPointer;
ItemNode itemNode;
long count;
if ((object)key == null)
throw new ArgumentNullException(nameof(key));
FailIfReadOnly();
Find(key, out lookupPointer, out itemPointer);
if (itemPointer >= m_headerNode.ItemSectionPointer)
count = m_headerNode.Count;
else
count = m_headerNode.Count + 1;
if (count > m_headerNode.Capacity * MaximumLoadFactor)
{
Grow();
Find(key, out lookupPointer, out itemPointer);
}
itemNode = new ItemNode();
itemNode.LookupPointer = lookupPointer;
itemNode.HashCode = m_keyComparer.GetHashCode(key);
itemNode.Key = key;
itemNode.Value = value;
m_fileStream.Seek(m_headerNode.EndOfFilePointer, SeekOrigin.Begin);
Write(itemNode);
Set(lookupPointer, m_headerNode.EndOfFilePointer, count);
if (itemPointer >= m_headerNode.ItemSectionPointer)
m_fragmentationCount++;
}
}
///
/// Gets or sets the size of the cache used
/// to store data from the file in memory.
///
public long CacheSize
{
get
{
if ((object)m_fileStream == null)
OpenImplicit();
// ReSharper disable once PossibleNullReferenceException
return m_fileStream.CacheSize;
}
set
{
if ((object)m_fileStream == null)
OpenImplicit();
// ReSharper disable once PossibleNullReferenceException
m_fileStream.CacheSize = value;
}
}
///
/// Gets the number of operations that fragment the
/// lookup table that have occurred since the last
/// time the lookup table was compacted.
///
///
/// This value is not stored in the file and may therefore
/// be inaccurate if the lookup table has not been compacted
/// since the last time it was opened.
///
public int FragmentationCount
{
get
{
return m_fragmentationCount;
}
}
private int LookupNodeSize
{
get
{
const int FixedSize = sizeof(long);
int variableSize = (m_lookupTableType == LookupTableType.HashSet) ? sizeof(int) : 0;
return FixedSize + variableSize;
}
}
#endregion
#region [ Methods ]
///
/// Opens the file backing this lookup table.
///
/// File is already open.
public void Open()
{
if ((object)m_fileStream != null)
throw new InvalidOperationException($"File is already open: {m_filePath}");
m_headerNode = new HeaderNode(m_lookupTableType);
m_journalNode = new JournalNode();
if (string.IsNullOrWhiteSpace(m_filePath))
m_filePath = Path.GetTempFileName();
string directory = Path.GetDirectoryName(m_filePath);
// Attempt to create the directory if it doesn't already exist
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
Directory.CreateDirectory(directory);
// Open the file in read/write mode
m_fileStream = new CachedFileStream(m_filePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
m_fileWriter = new BinaryWriter(m_fileStream);
m_fileReader = new BinaryReader(m_fileStream);
if (m_fileStream.Length > HeaderNode.FixedSize + JournalNode.FixedSize)
{
// Read the header node and the journal
// node from the start of the file
Read(m_headerNode);
Read(m_journalNode);
// If the journal node has an indicates that an operation was interrupted
// the last time this file was open, replay that operation to return the
// file to a consistent state
switch (m_journalNode.Operation)
{
case JournalNode.Set:
Set(m_journalNode.LookupPointer, m_journalNode.ItemPointer, m_journalNode.Sync);
break;
case JournalNode.Delete:
Delete(m_journalNode.LookupPointer, m_journalNode.Sync);
break;
case JournalNode.GrowLookupSection:
GrowLookupSection(m_journalNode.LookupPointer, m_journalNode.ItemPointer, m_journalNode.Sync);
break;
case JournalNode.RebuildLookupTable:
RebuildLookupTable(m_journalNode.LookupPointer);
break;
case JournalNode.WriteItemNodePointers:
WriteItemNodePointers(m_journalNode.LookupPointer, m_journalNode.ItemPointer, m_journalNode.Sync);
break;
case JournalNode.Truncate:
Truncate(m_journalNode.ItemPointer);
break;
case JournalNode.Clear:
Clear();
break;
}
}
else
{
// This will bring the lookup table to its initial
// state, with an empty lookup table and zero items
Clear();
}
}
///
/// Opens the file backing this lookup table in read-only mode.
///
/// File is already open or the file has a pending transaction that could not be completed.
/// The format of the file is invalid.
public void OpenRead()
{
void InternalOpenRead()
{
if ((object)m_fileStream != null)
throw new InvalidOperationException($"File is already open: {m_filePath}");
m_fileStream = new CachedFileStream(m_filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
m_fileReader = new BinaryReader(m_fileStream);
// Validate that the file format is valid
if (m_fileStream.Length <= HeaderNode.FixedSize + JournalNode.FixedSize)
throw new FormatException($"The format of the file is invalid: {m_filePath}");
// Read the header node from the start of the file
m_headerNode = new HeaderNode(m_lookupTableType);
m_journalNode = new JournalNode();
Read(m_headerNode);
Read(m_journalNode);
}
InternalOpenRead();
try
{
// There may be undefined behavior when reading from a lookup table with a
// pending transaction so we aggressively attempt to complete the transaction
// and then reopen the file in read-only mode
while (m_journalNode.Operation != JournalNode.None)
{
Close();
Open();
Close();
InternalOpenRead();
}
}
catch (Exception ex)
{
throw new InvalidOperationException($"Unable to complete the pending transaction in [{m_filePath}]: {ex.Message}", ex);
}
}
///
/// Adds an element with the provided key and value to the .
///
/// The object to use as the key of the element to add.
/// The object to use as the value of the element to add.
/// is null.
/// An element with the same key already exists in the .
/// The is read-only.
public void Add(TKey key, TValue value)
{
long lookupPointer;
long itemPointer;
ItemNode itemNode;
if ((object)key == null)
throw new ArgumentNullException(nameof(key));
FailIfReadOnly();
Find(key, out lookupPointer, out itemPointer);
if (itemPointer >= m_headerNode.ItemSectionPointer)
throw new ArgumentException($"An element with the same key ({key}) already exists: {m_filePath}");
if (m_headerNode.Count + 1L > m_headerNode.Capacity * MaximumLoadFactor)
{
Grow();
Find(key, out lookupPointer, out itemPointer);
}
itemNode = new ItemNode();
itemNode.LookupPointer = lookupPointer;
itemNode.HashCode = m_keyComparer.GetHashCode(key);
itemNode.Key = key;
itemNode.Value = value;
m_fileStream.Seek(m_headerNode.EndOfFilePointer, SeekOrigin.Begin);
Write(itemNode);
Set(lookupPointer, m_headerNode.EndOfFilePointer, m_headerNode.Count + 1);
}
///
/// Adds an element with the provided key and value to the .
///
/// The object to use as the key of the element to add.
/// The object to use as the value of the element to add.
/// True if the item was successfully added; false otherwise.
/// is null.
/// An element with the same key already exists in the .
/// The is read-only.
public bool TryAdd(TKey key, TValue value)
{
long lookupPointer;
long itemPointer;
ItemNode itemNode;
if ((object)key == null || IsReadOnly)
return false;
Find(key, out lookupPointer, out itemPointer);
if (itemPointer >= m_headerNode.ItemSectionPointer)
return false;
if (m_headerNode.Count + 1 > m_headerNode.Capacity * MaximumLoadFactor)
{
Grow();
Find(key, out lookupPointer, out itemPointer);
}
itemNode = new ItemNode();
itemNode.LookupPointer = lookupPointer;
itemNode.HashCode = m_keyComparer.GetHashCode(key);
itemNode.Key = key;
itemNode.Value = value;
m_fileStream.Seek(m_headerNode.EndOfFilePointer, SeekOrigin.Begin);
Write(itemNode);
Set(lookupPointer, m_headerNode.EndOfFilePointer, m_headerNode.Count + 1);
return true;
}
///
/// Removes the element with the specified key from the .
///
///
/// true if the element is successfully removed; otherwise, false.
/// This method also returns false if was not
/// found in the original .
///
/// The key of the element to remove.
/// is null.
/// The is read-only.
public bool Remove(TKey key)
{
long lookupPointer;
long itemPointer;
if ((object)key == null)
throw new ArgumentNullException(nameof(key));
FailIfReadOnly();
Find(key, out lookupPointer, out itemPointer);
if (itemPointer >= m_headerNode.ItemSectionPointer)
{
Delete(lookupPointer, m_headerNode.Count - 1);
m_fragmentationCount++;
return true;
}
return false;
}
///
/// Attempts to find the lookup node for the corresponding
/// key and, if found, marks the lookup node.
///
/// The key used to find the lookup node to be marked.
/// The is read-only.
public bool TryMark(TKey key)
{
long lookupPointer;
long itemPointer;
if (m_lookupTableType == LookupTableType.HashSet)
{
FailIfReadOnly();
Find(key, out lookupPointer, out itemPointer);
if (itemPointer >= m_headerNode.ItemSectionPointer)
{
m_fileStream.Seek(lookupPointer + sizeof(long), SeekOrigin.Begin);
m_fileWriter.Write(1);
return true;
}
}
return false;
}
///
/// Determines whether all occupied lookup nodes are marked.
///
/// True if all occupied lookup nodes are marked; false otherwise.
/// The is read-only.
public bool AllMarked()
{
long lookupPointer;
LookupNode lookupNode;
if (m_lookupTableType == LookupTableType.HashSet)
{
FailIfReadOnly();
lookupPointer = HeaderNode.FixedSize + JournalNode.FixedSize;
lookupNode = new LookupNode();
for (int i = 0; i < m_headerNode.Capacity; i++)
{
m_fileStream.Seek(lookupPointer, SeekOrigin.Begin);
Read(lookupNode);
if (lookupNode.ItemPointer >= m_headerNode.ItemSectionPointer && lookupNode.Marker == 0)
return false;
lookupPointer += LookupNodeSize;
}
}
return true;
}
///
/// Removes all unmarked nodes from the hash set.
///
/// The is read-only.
public void RemoveMarked()
{
long lookupPointer;
int mark;
if (m_lookupTableType == LookupTableType.HashSet)
{
FailIfReadOnly();
lookupPointer = HeaderNode.FixedSize + JournalNode.FixedSize;
for (int i = 0; i < m_headerNode.Capacity; i++)
{
m_fileStream.Seek(lookupPointer + sizeof(long), SeekOrigin.Begin);
mark = m_fileReader.ReadInt32();
if (mark != 0)
{
Delete(lookupPointer, m_headerNode.Count - 1);
m_fragmentationCount++;
}
lookupPointer += LookupNodeSize;
}
}
}
///
/// Removes all unmarked nodes from the hash set.
///
/// The is read-only.
public void RemoveUnmarked()
{
long lookupPointer;
LookupNode lookupNode;
if (m_lookupTableType == LookupTableType.HashSet)
{
FailIfReadOnly();
lookupPointer = HeaderNode.FixedSize + JournalNode.FixedSize;
lookupNode = new LookupNode();
for (int i = 0; i < m_headerNode.Capacity; i++)
{
m_fileStream.Seek(lookupPointer, SeekOrigin.Begin);
Read(lookupNode);
if (lookupNode.ItemPointer >= m_headerNode.ItemSectionPointer && lookupNode.Marker == 0)
{
Delete(lookupPointer, m_headerNode.Count - 1);
m_fragmentationCount++;
}
lookupPointer += LookupNodeSize;
}
}
}
///
/// Unmarks all lookup nodes in the hash set.
///
/// The is read-only.
public void UnmarkAll()
{
long lookupPointer;
if (m_lookupTableType == LookupTableType.HashSet)
{
FailIfReadOnly();
lookupPointer = HeaderNode.FixedSize + JournalNode.FixedSize;
for (int i = 0; i < m_headerNode.Capacity; i++)
{
m_fileStream.Seek(lookupPointer + sizeof(long), SeekOrigin.Begin);
m_fileWriter.Write(0);
lookupPointer += LookupNodeSize;
}
}
}
///
/// Determines whether the contains an element with the specified key.
///
///
/// true if the contains an element with the key; otherwise, false.
///
/// The key to locate in the .
/// is null.
public bool ContainsKey(TKey key)
{
long lookupPointer;
long itemPointer;
if ((object)key == null)
throw new ArgumentNullException(nameof(key));
if ((object)m_fileStream == null)
OpenImplicit();
Find(key, out lookupPointer, out itemPointer);
return (itemPointer >= m_headerNode.ItemSectionPointer);
}
///
/// Gets the value associated with the specified key.
///
///
/// true if the object that implements contains an element with the specified key; otherwise, false.
///
/// The key whose value to get.
/// When this method returns, the value associated with the specified key, if the key is found; otherwise, the default value for the type of the parameter. This parameter is passed uninitialized.
/// is null.
public bool TryGetValue(TKey key, out TValue value)
{
long lookupPointer;
long itemPointer;
if ((object)key == null)
throw new ArgumentNullException(nameof(key));
if ((object)m_fileStream == null)
OpenImplicit();
Find(key, out lookupPointer, out itemPointer);
if (itemPointer >= m_headerNode.ItemSectionPointer)
{
value = ReadValue();
return true;
}
value = default(TValue);
return false;
}
///
/// Removes all items from the .
///
/// The is read-only.
public void Clear()
{
FailIfReadOnly();
if (m_journalNode.Operation != JournalNode.Clear)
{
m_journalNode.Operation = JournalNode.Clear;
Write(m_journalNode);
}
// Truncate the file to eliminate the lookup table and item section
m_fileStream.SetLength(HeaderNode.FixedSize + JournalNode.FixedSize);
// Create a new header node and write it to the start of the file
m_headerNode.Count = 0L;
m_headerNode.Capacity = 16L;
m_headerNode.ItemSectionPointer = HeaderNode.FixedSize + JournalNode.FixedSize + LookupNodeSize * m_headerNode.Capacity;
m_headerNode.EndOfFilePointer = m_headerNode.ItemSectionPointer;
Write(m_headerNode);
// Create the new lookup table section
m_fileStream.SetLength(m_headerNode.ItemSectionPointer);
// Clearing the lookup table defragments the file
m_fragmentationCount = 0;
ClearJournalNode();
}
///
/// Returns an enumerator that iterates through the collection.
///
///
/// A that can be used to iterate through the collection.
///
/// 1
public IEnumerator> GetEnumerator()
{
ItemNode itemNode;
long lookupPointer;
long itemPointer;
long count;
if ((object)m_fileStream == null)
OpenImplicit();
itemNode = new ItemNode();
lookupPointer = HeaderNode.FixedSize + JournalNode.FixedSize;
count = 0L;
while (count < m_headerNode.Count)
{
itemPointer = ReadItemPointer(lookupPointer);
if (itemPointer >= m_headerNode.ItemSectionPointer)
{
// ReSharper disable once PossibleNullReferenceException
m_fileStream.Seek(itemPointer, SeekOrigin.Begin);
Read(itemNode);
yield return new KeyValuePair(itemNode.Key, itemNode.Value);
count++;
}
lookupPointer += LookupNodeSize;
}
}
///
/// Gets an enumerable used to iterate only the keys in the lookup table.
///
/// An enumerable used to iterate only the keys in the lookup table.
public IEnumerable GetKeys()
{
return new KeysEnumerable(GetKeysEnumerator);
}
///
/// Defragments the item section of the lookup table,
/// which gets fragmented after removing keys or updating values.
///
public void Compact()
{
FailIfReadOnly();
if (m_headerNode.ItemSectionPointer == m_headerNode.EndOfFilePointer)
return;
// Initialize item1, lookup1, and item2 pointers
long item1 = m_headerNode.ItemSectionPointer;
long lookup1 = ReadLookupPointer(item1);
long item2 = m_fileReader.ReadInt64();
// Determine whether item1 is an orphaned node
bool orphan1 = ReadItemPointer(lookup1) != item1;
// Byte array will be initialized to the
// right size and reused during compaction
byte[] bytes = null;
while (item2 < m_headerNode.EndOfFilePointer)
{
// Update lookup2 and item3 pointers
long lookup2 = ReadLookupPointer(item2);
long item3 = m_fileReader.ReadInt64();
// Determine whether item2 is an orphaned node
bool orphan2 = ReadItemPointer(lookup2) != item2;
if (!orphan1)
{
// Skip nodes that are not orphaned at
// the beginning of the item section
item1 = item2;
lookup1 = lookup2;
orphan1 = orphan2;
}
else
{
if (orphan2)
{
// Combine the orphaned nodes
WriteItemNodePointers(lookup1, item1, item3);
}
else
{
// Determine the length of the two nodes
int length1 = (int)(item2 - item1);
int length2 = (int)(item3 - item2);
// Allocate enough memory to move item2
if ((object)bytes == null || bytes.Length < length2)
bytes = new byte[length2];
// Read item2 into memory
m_fileStream.Seek(item2, SeekOrigin.Begin);
m_fileStream.Read(bytes, 0, length2);
// Check to see if item1 is at least large enough
// to fully contain item2 plus an orphaned node
if (length2 + 2 * sizeof(long) < length1)
{
// Write data to the body of the orphaned node
m_fileStream.Seek(item1 + 2 * sizeof(long), SeekOrigin.Begin);
m_fileStream.Write(bytes, 2 * sizeof(long), length2 - 2 * sizeof(long));
// Position nextItemPointer of the soon
// to be orphaned node, pointing at item2
m_fileWriter.Write(lookup1);
m_fileWriter.Write(item2);
// Update the pointers in the orphaned item node,
// then point the lookup node to the orphaned node
WriteItemNodePointers(lookup2, item1, item1 + length2);
Set(lookup2, item1, m_headerNode.Count);
// The left over empty space and the node at item2
// have become orphaned nodes so combine them
WriteItemNodePointers(lookup1, item1 + length2, item3);
// Update item1 for the next iteration
item1 += length2;
}
else
{
// Write item2 beyond the end of the item section
m_fileStream.Seek(m_headerNode.EndOfFilePointer, SeekOrigin.Begin);
m_fileStream.Write(bytes, 0, length2);
// Fix the nextItemPointer
m_fileStream.Seek(m_headerNode.EndOfFilePointer + sizeof(long), SeekOrigin.Begin);
m_fileWriter.Write(m_headerNode.EndOfFilePointer + length2);
// Update the lookup node to point to the new location for item2
Set(lookup2, m_headerNode.EndOfFilePointer, m_headerNode.Count);
// Combine the orphaned item1 and the newly orphaned item2
WriteItemNodePointers(lookup1, item1, item3);
}
}
}
// Update item2 for the next iteration
item2 = item3;
}
if (orphan1)
Truncate(item1);
// Set fragmentation count to zero
// once the file has been compacted
m_fragmentationCount = 0;
}
///
/// Closes the file backing this lookup table.
///
public void Close()
{
using (m_fileStream)
using (m_fileWriter)
using (m_fileReader)
{
m_fileStream = null;
m_fileWriter = null;
m_fileReader = null;
}
// Set fragmentation count to
// zero when closing the file
m_fragmentationCount = 0;
}
///
/// Releases all the resources used by the object.
///
public void Dispose()
{
Close();
}
private void OpenImplicit()
{
// When accessing the lookup table without explicitly opening the file,
// we cannot determine the level of access required by all operations
// that will be performed against the lookup table so we default to write
// access and fall back on read access if necessary
try { Open(); }
catch (UnauthorizedAccessException) { OpenRead(); }
}
private void Grow()
{
ItemNode itemNode = new ItemNode();
long newCapacity = 2 * m_headerNode.Capacity;
long lookupTableSize = newCapacity * LookupNodeSize;
long minimumItemSectionPointer = HeaderNode.FixedSize + JournalNode.FixedSize + lookupTableSize;
long itemPointer = m_headerNode.ItemSectionPointer;
long copyPointer = m_headerNode.EndOfFilePointer;
// If the lookup table is growing beyond the end of the file,
// save time by writing a nextItemPointer beyond the end of the file.
// This will allow us to begin copying item nodes directly to the new
// item section so that we don't have to move item nodes multiple times
if (minimumItemSectionPointer > m_headerNode.EndOfFilePointer + sizeof(long) * 2)
{
m_fileStream.Seek(m_headerNode.EndOfFilePointer, SeekOrigin.Begin);
m_fileWriter.Write(0L);
m_fileWriter.Write(minimumItemSectionPointer);
copyPointer = minimumItemSectionPointer;
}
// Begin copying item nodes to the end of the file
// to make room for the lookup table to grow
while (itemPointer < minimumItemSectionPointer)
{
// Read the two pointers at the start of the item node
m_fileStream.Seek(itemPointer, SeekOrigin.Begin);
itemNode.LookupPointer = m_fileReader.ReadInt64();
itemNode.NextItemPointer = m_fileReader.ReadInt64();
itemNode.HashCode = m_fileReader.ReadInt32();
// If the lookup node referenced by this item node does not point back
// to the item node, then this is an orphaned node and may be skipped
if (ReadItemPointer(itemNode.LookupPointer) != itemPointer)
{
itemPointer = itemNode.NextItemPointer;
continue;
}
// Jump back to the item node and read the key and value,
// then update the item pointer to point to the next item after this one
m_fileStream.Seek(itemPointer + ItemNode.FixedSize, SeekOrigin.Begin);
itemNode.Key = ReadKey();
itemNode.Value = ReadValue();
itemPointer = itemNode.NextItemPointer;
// Write the item node to the end of the file,
// then update the copy pointer to point to the
// next available location at the end of the file
m_fileStream.Seek(copyPointer, SeekOrigin.Begin);
Write(itemNode);
copyPointer = m_fileStream.Position;
}
// Use the GrowLookupSection operation to point lookup nodes
// to the copied item nodes and move the item section pointer
GrowLookupSection(itemPointer, m_headerNode.EndOfFilePointer, copyPointer);
// This will compact the item section, clear the existing
// lookup table, rebuild the lookup table from the lookup
// pointers in the item section, and increase the capacity
// of the lookup tables
RebuildLookupTable(newCapacity);
}
private long FindEndOfChain(int hashCode, long capacity)
{
long position = GetPosition(GetFirstHash(hashCode), capacity);
long collisionOffset = GetCollisionOffset(hashCode);
long lookupPointer = GetLookupPointer(position);
while (ReadItemPointer(lookupPointer) >= m_headerNode.ItemSectionPointer)
{
position = GetPosition(position + collisionOffset, capacity);
lookupPointer = GetLookupPointer(position);
}
return lookupPointer;
}
private void Find(TKey key, out long lookupPointer, out long itemPointer)
{
int hashCode = m_keyComparer.GetHashCode(key);
long firstHash = GetFirstHash(hashCode);
long collisionOffset = GetCollisionOffset(hashCode);
long position = GetPosition(firstHash);
long emptyPointer = 0L;
int emptyNodes = 0;
lookupPointer = GetLookupPointer(position);
itemPointer = ReadItemPointer(lookupPointer);
// Loop until the item pointer
// is pointing to the header node
while (itemPointer > 0L)
{
if (itemPointer >= m_headerNode.ItemSectionPointer)
{
// Determine if the item pointed to by
// lookupPointer is the one we are trying to find
m_fileStream.Seek(itemPointer + ItemNode.FixedSize, SeekOrigin.Begin);
if (m_keyComparer.Equals(key, ReadKey()))
return;
}
else if (emptyPointer == 0L)
{
// Because the item pointer is not pointing to the
// item section of the file, this is an empty slot
// that may be used for a new item
emptyPointer = lookupPointer;
emptyNodes++;
}
else
{
// Track the number of empty nodes in the chain to
// determine whether the lookup tables need to be rebuilt
emptyNodes++;
}
// If the lookup node has never been occupied,
// it is the end of the chain
if (itemPointer == 0L)
break;
if (emptyNodes > MaximumChainedEmptyNodes)
{
// Rebuild the lookup table and begin searching
// again from the beginning of the chain
RebuildLookupTable(m_headerNode.Capacity);
position = GetPosition(firstHash);
emptyPointer = 0L;
emptyNodes = 0;
}
else
{
// Update the position using the collision offset
// to find the next lookup node in the chain
position = GetPosition(position + collisionOffset);
}
lookupPointer = GetLookupPointer(position);
itemPointer = ReadItemPointer(lookupPointer);
}
// If we made it here, it means that the item wasn't found in the lookup tables
//
// If the chain is not empty, an empty node should have been found in it;
// otherwise, lookupPointer should already be pointing to the first node in the chain
if (emptyPointer > 0L)
lookupPointer = emptyPointer;
// Set itemPointer to point to the header
// node to indicate the item was not found
itemPointer = 0L;
}
private void Set(long lookupPointer, long itemPointer, long count)
{
long nextItemPointer;
// Write the set operation to the journal node
m_journalNode.Operation = JournalNode.Set;
m_journalNode.LookupPointer = lookupPointer;
m_journalNode.ItemPointer = itemPointer;
m_journalNode.Sync = count;
Write(m_journalNode);
// Perform the set operation
WriteItemPointer(lookupPointer, itemPointer);
nextItemPointer = ReadNextItemPointer(itemPointer);
if (nextItemPointer > m_headerNode.EndOfFilePointer)
m_headerNode.EndOfFilePointer = nextItemPointer;
m_headerNode.Count = count;
Write(m_headerNode);
ClearJournalNode();
}
private void Delete(long lookupPointer, long count)
{
// Write the delete operation to the journal node
m_journalNode.Operation = JournalNode.Delete;
m_journalNode.LookupPointer = lookupPointer;
m_journalNode.Sync = count;
Write(m_journalNode);
// Perform the delete operation
WriteItemPointer(lookupPointer, 1L);
m_headerNode.Count = count;
Write(m_headerNode);
ClearJournalNode();
}
private void GrowLookupSection(long itemSectionPointer, long endOfFilePointer, long newEndOfFilePointer)
{
long lookupPointer;
long itemPointer;
if (m_journalNode.Operation != JournalNode.GrowLookupSection)
{
// Write the grow operation to the journal node
m_journalNode.Operation = JournalNode.GrowLookupSection;
m_journalNode.ItemPointer = itemSectionPointer;
Write(m_journalNode);
}
// Perform the grow operation
itemPointer = endOfFilePointer;
while (itemPointer < newEndOfFilePointer)
{
lookupPointer = ReadLookupPointer(itemPointer);
WriteItemPointer(lookupPointer, itemPointer);
m_fileStream.Seek(itemPointer + sizeof(long), SeekOrigin.Begin);
itemPointer = m_fileReader.ReadInt64();
}
m_headerNode.ItemSectionPointer = itemSectionPointer;
m_headerNode.EndOfFilePointer = newEndOfFilePointer;
Write(m_headerNode);
ClearJournalNode();
}
private void RebuildLookupTable(long capacity)
{
LookupNode emptyNode;
long lookupPointer;
long itemPointer;
long nextItemPointer;
int hashCode;
if (m_journalNode.Operation != JournalNode.RebuildLookupTable)
{
// Item section needs to be compacted
// before this operation can work
Compact();
// Write the grow operation to the journal node
m_journalNode.Operation = JournalNode.RebuildLookupTable;
m_journalNode.LookupPointer = capacity;
Write(m_journalNode);
}
// Perform the grow operation
emptyNode = new LookupNode();
m_fileStream.Seek(HeaderNode.FixedSize + JournalNode.FixedSize, SeekOrigin.Begin);
for (int i = 0; i < capacity; i++)
Write(emptyNode);
itemPointer = m_headerNode.ItemSectionPointer;
while (itemPointer < m_headerNode.EndOfFilePointer)
{
m_fileStream.Seek(itemPointer + sizeof(long), SeekOrigin.Begin);
nextItemPointer = m_fileReader.ReadInt64();
hashCode = m_fileReader.ReadInt32();
lookupPointer = FindEndOfChain(hashCode, capacity);
WriteLookupPointer(lookupPointer, itemPointer);
WriteItemPointer(lookupPointer, itemPointer);
itemPointer = nextItemPointer;
}
m_headerNode.Capacity = capacity;
Write(m_headerNode);
ClearJournalNode();
}
private void WriteItemNodePointers(long lookupPointer, long itemPointer, long nextItemPointer)
{
// Write the grow operation to the journal node
m_journalNode.Operation = JournalNode.WriteItemNodePointers;
m_journalNode.LookupPointer = lookupPointer;
m_journalNode.ItemPointer = itemPointer;
m_journalNode.Sync = nextItemPointer;
Write(m_journalNode);
// Perform the write operation
m_fileStream.Seek(itemPointer, SeekOrigin.Begin);
m_fileWriter.Write(lookupPointer);
m_fileWriter.Write(nextItemPointer);
ClearJournalNode();
}
private void Truncate(long itemPointer)
{
// Write the grow operation to the journal node
m_journalNode.Operation = JournalNode.Truncate;
m_journalNode.ItemPointer = itemPointer;
Write(m_journalNode);
// Perform the truncate operation
m_headerNode.EndOfFilePointer = itemPointer;
Write(m_headerNode);
m_fileStream.SetLength(itemPointer);
ClearJournalNode();
}
private void ClearJournalNode()
{
m_journalNode.Operation = JournalNode.None;
m_journalNode.LookupPointer = 0L;
m_journalNode.ItemPointer = 0L;
m_journalNode.Sync = 0;
Write(m_journalNode);
}
private long GetPosition(long hashCode)
{
return GetPosition(hashCode, m_headerNode.Capacity);
}
private long GetPosition(long hashCode, long capacity)
{
return hashCode % capacity;
}
private long GetLookupPointer(long position)
{
return HeaderNode.FixedSize
+ JournalNode.FixedSize
+ LookupNodeSize * position;
}
private void Write(HeaderNode node)
{
m_fileStream.Seek(0, SeekOrigin.Begin);
m_fileStream.Write(m_headerNode.Signature, 0, HeaderNode.SignatureSize);
m_fileWriter.Write(node.Count);
m_fileWriter.Write(node.Capacity);
m_fileWriter.Write(node.ItemSectionPointer);
m_fileWriter.Write(node.EndOfFilePointer);
}
private void Write(JournalNode node)
{
Crc32 checksum = new Crc32();
checksum.Update(node.Operation);
checksum.Update((int)(node.LookupPointer >> sizeof(int)));
checksum.Update((int)node.LookupPointer);
checksum.Update((int)(node.ItemPointer >> sizeof(int)));
checksum.Update((int)node.ItemPointer);
checksum.Update((int)(node.Sync >> sizeof(int)));
checksum.Update((int)node.Sync);
node.Checksum = (int)checksum.Value;
m_fileStream.Flush();
m_fileStream.Seek(HeaderNode.FixedSize, SeekOrigin.Begin);
m_fileWriter.Write(node.Operation);
m_fileWriter.Write(node.LookupPointer);
m_fileWriter.Write(node.ItemPointer);
m_fileWriter.Write(node.Sync);
m_fileWriter.Write(node.Checksum);
m_fileStream.Flush();
}
private void Write(LookupNode node)
{
m_fileWriter.Write(node.ItemPointer);
if (m_lookupTableType == LookupTableType.HashSet)
m_fileWriter.Write(node.Marker);
}
private void Write(ItemNode node)
{
long start;
long end;
start = m_fileStream.Position;
m_fileWriter.Write(node.LookupPointer);
m_fileStream.Seek(sizeof(long), SeekOrigin.Current);
m_fileWriter.Write(node.HashCode);
WriteKeyAction(m_fileStream, node.Key);
if (m_lookupTableType == LookupTableType.Dictionary)
WriteValueAction(m_fileStream, node.Value);
end = m_fileStream.Position;
m_fileStream.Seek(start + sizeof(long), SeekOrigin.Begin);
m_fileWriter.Write(end);
m_fileStream.Seek(end, SeekOrigin.Begin);
}
private void WriteItemPointer(long lookupPointer, long itemPointer)
{
m_fileStream.Seek(lookupPointer, SeekOrigin.Begin);
m_fileWriter.Write(itemPointer);
}
private void WriteLookupPointer(long lookupPointer, long itemPointer)
{
m_fileStream.Seek(itemPointer, SeekOrigin.Begin);
m_fileWriter.Write(lookupPointer);
}
private void Read(HeaderNode node)
{
m_fileStream.Read(node.Signature, 0, HeaderNode.SignatureSize);
node.Count = m_fileReader.ReadInt64();
node.Capacity = m_fileReader.ReadInt64();
node.ItemSectionPointer = m_fileReader.ReadInt64();
node.EndOfFilePointer = m_fileReader.ReadInt64();
}
private void Read(JournalNode node)
{
Crc32 checksum = new Crc32();
node.Operation = m_fileReader.ReadInt32();
node.LookupPointer = m_fileReader.ReadInt64();
node.ItemPointer = m_fileReader.ReadInt64();
node.Sync = m_fileReader.ReadInt64();
node.Checksum = m_fileReader.ReadInt32();
checksum.Update(node.Operation);
checksum.Update((int)(node.LookupPointer >> sizeof(int)));
checksum.Update((int)node.LookupPointer);
checksum.Update((int)(node.ItemPointer >> sizeof(int)));
checksum.Update((int)node.ItemPointer);
checksum.Update((int)(node.Sync >> sizeof(int)));
checksum.Update((int)node.Sync);
if (node.Checksum != (int)checksum.Value)
{
node.Operation = JournalNode.None;
node.Sync = 0;
node.LookupPointer = 0L;
node.ItemPointer = 0L;
if (!IsReadOnly)
Write(node);
}
}
private void Read(LookupNode node)
{
node.ItemPointer = m_fileReader.ReadInt64();
if (m_lookupTableType == LookupTableType.HashSet)
node.Marker = m_fileReader.ReadInt32();
}
private void Read(ItemNode node)
{
node.LookupPointer = m_fileReader.ReadInt64();
node.NextItemPointer = m_fileReader.ReadInt64();
node.HashCode = m_fileReader.ReadInt32();
node.Key = ReadKey();
node.Value = ReadValue();
}
private long ReadItemPointer(long lookupPointer)
{
if (lookupPointer >= m_headerNode.ItemSectionPointer)
return 0L;
m_fileStream.Seek(lookupPointer, SeekOrigin.Begin);
return m_fileReader.ReadInt64();
}
private long ReadLookupPointer(long itemPointer)
{
m_fileStream.Seek(itemPointer, SeekOrigin.Begin);
return m_fileReader.ReadInt64();
}
private long ReadNextItemPointer(long itemPointer)
{
m_fileStream.Seek(itemPointer + sizeof(long), SeekOrigin.Begin);
return m_fileReader.ReadInt64();
}
private TKey ReadKey()
{
return ReadKeyFunc(m_fileStream);
}
private TValue ReadValue()
{
if (m_lookupTableType == LookupTableType.Dictionary)
return ReadValueFunc(m_fileStream);
return default(TValue);
}
private long GetFirstHash(int hashCode)
{
uint i = (uint)hashCode;
long hash = 17L;
while (i > 0)
{
hash = hash * 23L + (i & 0xF);
i >>= 4;
}
return hash;
}
private long GetCollisionOffset(int hashCode)
{
uint i = (uint)hashCode;
long hash = 13L;
while (i > 0)
{
hash = hash * 29L + (i & 0xF);
i >>= 4;
}
return hash | 1L;
}
private IEnumerator GetKeysEnumerator()
{
long lookupPointer;
long itemPointer;
long count;
if ((object)m_fileStream == null)
OpenImplicit();
lookupPointer = HeaderNode.FixedSize + JournalNode.FixedSize;
count = 0L;
while (count < m_headerNode.Count)
{
itemPointer = ReadItemPointer(lookupPointer);
if (itemPointer >= m_headerNode.ItemSectionPointer)
{
// ReSharper disable once PossibleNullReferenceException
m_fileStream.Seek(itemPointer + ItemNode.FixedSize, SeekOrigin.Begin);
yield return ReadKey();
count++;
}
lookupPointer += LookupNodeSize;
}
}
private void FailIfReadOnly()
{
if (IsReadOnly)
throw new NotSupportedException($"Unable to modify read-only lookup table: {m_filePath}");
}
///
/// Returns an enumerator that iterates through a collection.
///
///
/// An object that can be used to iterate through the collection.
///
/// 2
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
#region [ Static ]
// Static Fields
// ReSharper disable StaticFieldInGenericType
private const BindingFlags Flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
private static readonly Type[] Types = { typeof(Stream) };
private static readonly BinaryFormatter Formatter;
private static readonly Action WriteKeyAction;
private static readonly Action WriteValueAction;
private static readonly Func ReadKeyFunc;
private static readonly Func ReadValueFunc;
// ReSharper restore StaticFieldInGenericType
// Static Constructor
static FileBackedLookupTable()
{
Formatter = new BinaryFormatter();
WriteKeyAction = GetWriteMethod();
WriteValueAction = GetWriteMethod();
ReadKeyFunc = GetReadMethod();
ReadValueFunc = GetReadMethod();
if ((object)WriteKeyAction == null || (object)ReadKeyFunc == null)
{
if (typeof(TKey).IsSerializable)
{
WriteKeyAction = (stream, key) => Formatter.Serialize(stream, key);
ReadKeyFunc = stream => (TKey)Formatter.Deserialize(stream);
}
}
if ((object)WriteValueAction == null || (object)ReadValueFunc == null)
{
if (typeof(TValue).IsSerializable)
{
WriteValueAction = (stream, value) => Formatter.Serialize(stream, value);
ReadValueFunc = stream => (TValue)Formatter.Deserialize(stream);
}
}
}
// Static Methods
private static Action GetWriteMethod()
{
Type type = typeof(T);
MethodInfo method = type.GetMethod("WriteTo", Flags, null, Types, null);
Action action;
if ((object)method == null)
return null;
action = (Action)Delegate.CreateDelegate(typeof(Action), method);
return (stream, obj) => action(obj, stream);
}
private static Func GetReadMethod()
{
Type type = typeof(T);
ConstructorInfo constructor = type.GetConstructor(Flags, null, Types, null);
MethodInfo method;
if ((object)constructor == null)
{
constructor = type.GetConstructor(Flags, null, Type.EmptyTypes, null);
if ((object)constructor == null)
return null;
method = type.GetMethod("ReadFrom", Flags, null, Types, null);
if ((object)method == null)
return null;
return stream =>
{
T obj = Activator.CreateInstance();
method.Invoke(obj, new object[] { stream });
return obj;
};
}
List parameterExpressions = Types.Select(Expression.Parameter).ToList();
NewExpression newExpression = Expression.New(constructor, parameterExpressions);
LambdaExpression lambdaExpression = Expression.Lambda(typeof(Func), newExpression, parameterExpressions);
return (Func)lambdaExpression.Compile();
}
#endregion
}
}