/*
* LargeArray.cs
*
* Copyright © 2009
* Diversified Technical Systems, Inc.
* All Rights Reserved
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Text;
using DTS.Common.Utilities;
using DTS.Common.Utilities.DotNetProgrammingConstructs;
using DTS.Common.Utilities.Logging;
namespace DTS.Common.Utilities.IO.MemoryMap
{
///
/// A generic memory-mapped array.
///
///
///
/// The type of object to be contained in this collection.
///
///
public abstract partial class LargeArray
: Exceptional,
IDisposable,
ICollection,
IEnumerable,
ICloneable,
IList
where T : struct
{
///
/// Initialize an instance of a .
///
///
///
/// The size of the collection.
///
///
public LargeArray(ulong size)
: this(size, null, null)
{
}
///
/// Initialize an instance of a .
///
///
///
/// The size of the collection.
///
///
///
/// The name of the directory where this LargeArray will store the temporary
/// serializations of its items. If null, then the default location will be used.
///
///
///
/// The file name prefix under which this LargeArray will store temporary
/// serializations of its items. Initialized to DefaultFilePrefix's value at object
/// creation, unless an alternate is specified by the user. If null, then the default prefix
/// will be used.
///
///
public LargeArray(ulong size, string scratchFileDirectory, string scratchFilePrefix)
{
try
{
IsMemoryMapped = false;
Size = size;
ViewSize = 0xFFFF; //50 * 1024; //50 * 1024; // 50 * 1024 * 1024;
ScratchDirectory = scratchFileDirectory ?? DefaultScratchDirectory;
FilePrefix = scratchFilePrefix ?? DefaultFilePrefix;
IsMemoryMapped = CreateScratchFile(size, ScratchDirectory, FilePrefix);
}
catch (System.Exception ex)
{
throw new Exception("encountered problem constructing " + GetType().FullName, ex);
}
}
///
/// Finalizer. Can't find anyplace else to put this file removal and have it
/// work, since something in the mapping code is tenaciously holding onto it.
///
~LargeArray()
{
//
// Delete our scratch file.
//
File.Delete(FullScratchFilename);
}
///
/// A shared object to be shared by all instances
/// of to ensure that no two try to use the same scratch filename.
///
protected static NameGeneratorLock NameLock
{
get
{
try { return _NameLock; }
catch (System.Exception ex)
{
throw new Exception("encountered problem getting name generator lock", ex);
}
}
}
private static readonly NameGeneratorLock _NameLock = new NameGeneratorLock();
///
/// Get the next unique scratch file number designation.
///
protected static ulong NextScratchFileNumberDesignation
{
get
{
try
{
lock (NameLock)
{
return _NextScratchFileNumberDesignation++;
}
}
catch (System.Exception ex)
{
throw new Exception("encountered problem getting next scratch file number designation", ex);
}
}
}
private static ulong _NextScratchFileNumberDesignation = 0;
///
/// The default name of the directory where MegArrays will store the temporary
/// serializations of their items.
///
public static string DefaultScratchDirectory
{
get => _DefaultScratchDirectory.Value;
set => _DefaultScratchDirectory.Value = value;
}
private static readonly Property _DefaultScratchDirectory
= new Property(
typeof(LargeArray).FullName + ".DefaultScratchDirectory",
"C:\\\\Temp\\",
true
);
///
/// The name of the directory where this LargeArray will store its temporary
/// serializations of its items. Initialized to DefaultScratchDirectory's value at object
/// creation, unless an alternate is specified by the user.
///
public string ScratchDirectory
{
get => _ScratchDirectory.Value;
set => _ScratchDirectory.Value = value;
}
private readonly Property _ScratchDirectory
= new Property(
typeof(LargeArray).FullName + ".ScratchDirectory",
null,
false
);
///
/// The default prefix name for MegArrays temporary serializations.
///
public static string DefaultFilePrefix
{
get => _DefaultFilePrefix.Value;
set => _DefaultFilePrefix.Value = value;
}
private static readonly Property _DefaultFilePrefix
= new Property(
typeof(LargeArray).FullName + ".DefaultFilePrefix",
"largearray.tmp.",
true
);
///
/// The file name prefix under which this LargeArray will store temporary
/// serializations of its items. Initialized to DefaultFilePrefix's value at object
/// creation, unless an alternate is specified by the user.
///
public string FilePrefix
{
get => _FilePrefix.Value;
set => _FilePrefix.Value = value;
}
private readonly Property _FilePrefix
= new Property(
typeof(LargeArray).FullName + ".FilePrefix",
null,
false
);
///
/// Get the filename that this will be
/// using for its memory-mapped serialization file.
///
public string ScratchFilename
{
get
{
try
{
if (null == _ScratchFilename)
_ScratchFilename = GenerateNewScratchFilename();
return _ScratchFilename;
}
catch (System.Exception ex)
{
throw new Exception("encountered problem getting scratch filename property for " + GetType().FullName, ex);
}
}
}
private string _ScratchFilename = null;
///
/// Get the fully qualified filename that this
/// will be using for its memory-mapped serialization file.
///
public string FullScratchFilename
{
get
{
try
{
if (null == _FullScratchFilename)
_FullScratchFilename = ScratchDirectory + ScratchFilename;
return _FullScratchFilename;
}
catch (System.Exception ex)
{
throw new Exception("encountered problem getting fully qualified scratch filename property for " + GetType().FullName, ex);
}
}
}
private string _FullScratchFilename = null;
///
/// Generate a new unique filename.
///
///
///
/// A unique filename.
///
///
private string GenerateNewScratchFilename()
{
try
{
return FilePrefix + NextScratchFileNumberDesignation.ToString();
}
catch (System.Exception ex)
{
throw new Exception("encounterd problem generating new scratch filename", ex);
}
}
///
/// Get the maximum size of this .
///
public ulong Size
{
get;
}
///
/// Get flag indicating whether or not this is
/// currently associated with a disk file.
///
private bool IsMemoryMapped
{
get;
set;
}
///
/// Get the view size used by our memory mapping mechanism.
///
protected uint ViewSize
{
get;
}
///
/// The size of the basic datum type stored by this object.
///
public abstract uint DatumSize
{
get;
}
///
/// The value to be substituted for all array values when "clear" is invoked.
///
protected abstract T DatumClearValue
{
get;
}
///
/// Create the scratch file that will store this object's items.
///
///
///
/// The size of the scratch file to be created.
///
///
///
/// The path of the directory to contain the newly-created scratch
/// file. It will be created if it does not already exist.
///
///
///
/// The name of the file to be created.
///
///
///
/// true if the file has been successfully created, false otherwise.
///
///
private bool CreateScratchFile(ulong size, string directory, string filename)
{
try
{
// Make sure we have someplace to put this file.
if (!Directory.Exists(directory))
Directory.CreateDirectory(directory);
// Other files of this name should not exist.
if (File.Exists(filename))
throw new ScratchFileAlreadyExistsException("the scratch file \"" + FullScratchFilename + "\" already exists");
APILogger.Log("writing 35 ", FullScratchFilename);
lock (this)
{
using (var fileWriter = new BinaryWriter(new FileStream(FullScratchFilename, FileMode.Create)))
{
var byteSize = Size * DatumSize;
for (ulong i = 0; i < byteSize; i++)
fileWriter.Write((Byte)0x0);
}
}
return true;
}
catch (System.Exception ex)
{
throw new Exception("encountered problem setting up scratch file for " + GetType().FullName, ex);
}
}
///
/// Create a file map view array to associate this object with a file representation.
///
///
///
/// A association with with object's data file.
///
///
protected FileMapViewArray ViewArray
{
get
{
try
{
if (!IsMemoryMapped)
throw new Exception();
else
{
if (!_ViewArray.IsInitialized)
{
var fileInfo = new FileInfo(FullScratchFilename);
// Make a new mapViewArray for the file with proper offset and
// readonly privilages to protect sample integrity.
_ViewArray.Value = new FileMapViewArray(Path.GetFullPath(FullScratchFilename),
MapAccess.FileMapAllAccess,
MapProtection.PageReadWrite,
0,
(fileInfo.Length),
(int)ViewSize);
}
return _ViewArray.Value;
}
}
catch (System.Exception ex)
{
throw new Exception("encountered problem getting view array", ex);
}
}
}
private readonly Property _ViewArray
= new Property(
typeof(LargeArray).FullName + ".FileMap",
null,
false
);
///
/// Get the datum at the specified index.
///
///
///
/// The index of the datum sought.
///
///
///
/// The datum of type T at the specified index of the memory mapped file.
///
///
protected abstract T GetDatumAtIndex(ulong index);
///
/// Set the specified index to the specified datum.
///
///
///
/// The datum to be inserted at the specified index.
///
///
///
/// The index of the specified datum's destination.
///
///
protected abstract void SetDatumAtIndex(T datum, ulong index);
#region IDisposable Interface
// *********************************************************************
// ******************** BEGIN IDisposable Interface ********************
// *********************************************************************
///
/// Let go our our resources.
///
public void Dispose()
{
try
{
if (IsMemoryMapped)
{
ViewArray.Dispose();
IsMemoryMapped = false;
}
}
catch (System.Exception ex)
{
throw new Exception("encountered problem disposing of resources for " + GetType().FullName, ex);
}
}
// *******************************************************************
// ******************** END IDisposable Interface ********************
// *******************************************************************
#endregion
#region ICollection Interface
// *********************************************************************
// ******************** BEGIN ICollection Interface ********************
// *********************************************************************
///
/// Gets the number of elements contained in the .
///
///
public int Count
{
get
{
try
{
if (LargeCount > int.MaxValue)
throw new LargeOverflowException("\"large\" ulong-sized count is too big to cast to int-sized version");
else return (int)LargeCount;
}
catch (System.Exception ex)
{
throw new Exception("encountered problem getting item count for " + GetType().FullName, ex);
}
}
}
///
/// Gets a value indicating whether access to the
/// is synchronized (thread safe). Returns true if access to the
/// is synchronized (thread safe); otherwise, false.
///
///
public bool IsSynchronized
{
get
{
try
{
return true;
}
catch (System.Exception ex)
{
throw new Exception("encountered problem getting synchronization capability for " + GetType().FullName, ex);
}
}
}
///
/// Get an object that can be used to synchronize access to the System.Collections.ICollection.
///
public object SyncRoot
{
get
{
try
{
return this;
}
catch (System.Exception ex)
{
throw new Exception("encountered problem getting synchronization object for " + GetType().FullName, ex);
}
}
}
///
/// Copies the elements of the to a ,
/// starting at a particular index. If the array is too small
/// (we're storing more than max int items) then an exception will be thrown.
///
///
///
/// The one-dimensional that is the destination of the elements
/// copied from System.Collections.ICollection. The must have zero-based
/// indexing.
///
///
///
/// The zero-based index inn the array at which copying begins.
///
///
///
/// The array is null.
///
///
///
/// The index is less than zero.
///
///
///
/// The array is multidimensional OR...
/// the index is equal to or greater than the length of the array OR...
/// the number of elements in the source System.Collections.ICollection is greater than the available
/// space from index to the end of the destination array.
///
///
///
/// The type of the source System.Collections.ICollection cannot be cast automatically
/// to the type of the destination array.
///
///
public void CopyTo(Array array, int index)
{
try
{
if (null == array)
throw new ArgumentNullException("cannot copy to null array reference");
else if (index < 0)
throw new ArgumentOutOfRangeException("cannot copy from negative index (" + index.ToString() + ")");
else if (array.Rank > 1)
throw new ArgumentException("cannot copy to multidimensional array (parameter array rank is " + array.Rank.ToString() + ")");
else if ((ulong)(array.Length - index) < LargeCount)
throw new ArgumentException("target array is too small (" + (array.Length - index).ToString() + " < " + LargeCount.ToString() + ")");
else
{
ulong source_index;
int target_index;
for (source_index = 0, target_index = index;
source_index < LargeCount;
target_index++, source_index++)
{
((object[])array)[target_index] = this[source_index];
}
}
}
catch (ArgumentNullException)
{
throw;
}
catch (ArgumentOutOfRangeException)
{
throw;
}
catch (ArgumentException)
{
throw;
}
catch (System.Exception ex)
{
throw new Exception("encountered problem copying to " + GetType().FullName, ex);
}
}
// *******************************************************************
// ******************** END ICollection Interface ********************
// *******************************************************************
#endregion
#region IEnumerable Interface
// *********************************************************************
// ******************** BEGIN IEnumerable Interface ********************
// *********************************************************************
///
/// Returns an enumerator that iterates through a collection.
///
///
///
/// An System.Collections.IEnumerator object that can be used to iterate through
/// the collection.
///
///
public IEnumerator GetEnumerator()
{
try
{
return new Enumerator(this);
}
catch (System.Exception ex)
{
throw new Exception("encountered problem getting iterator for " + GetType().FullName, ex);
}
}
// *******************************************************************
// ******************** END IEnumerable Interface ********************
// *******************************************************************
#endregion
#region IList Interface
// ***************************************************************
// ******************** BEGIN IList Interface ********************
// ***************************************************************
///
/// Gets a value indicating whether the has a fixed size.
/// Returns true if it has a fixed size; otherwise, false.
///
public bool IsFixedSize
{
get
{
try
{
return true;
}
catch (System.Exception ex)
{
throw new Exception("encountered problem determining whether " + GetType().FullName + " has fixed size", ex);
}
}
}
///
/// Get a value indicating whether or not this
/// object is read-only. Returns true if it is read-only; false otherwise.
///
public bool IsReadOnly
{
get
{
try
{
return false;
}
catch (System.Exception ex)
{
throw new Exception("encountered problem determining read-only-ness of " + GetType().FullName, ex);
}
}
}
///
/// Get/set the element at the specified index.
///
///
///
/// The zero-based index of the element to gat or sat.
///
///
///
/// The element at the specified index.
///
///
///
/// The index is not a valid index in the .
///
///
///
/// The property is not set and the is read-only.
///
///
public virtual object this[int index]
{
get
{
try
{
if (index < 0)
throw new ArgumentOutOfRangeException("cannot access element at negative index (" + index.ToString() + ")");
else return this[(ulong)index];
}
catch (ArgumentOutOfRangeException)
{
throw;
}
catch (NotSupportedException)
{
throw;
}
catch (System.Exception ex)
{
throw new Exception("encountered problem getting item at index " + index.ToString() + " in " + GetType().FullName, ex);
}
}
set
{
try
{
if (index < 0)
throw new ArgumentOutOfRangeException("cannot access element at negative index (" + index.ToString() + ")");
else if (IsReadOnly)
throw new NotSupportedException("cannot assign value to read-only collection");
else this[(ulong)index] = (T)value;
}
catch (ArgumentOutOfRangeException)
{
throw;
}
catch (NotSupportedException)
{
throw;
}
catch (System.Exception ex)
{
throw new Exception("encountered problem setting item at index " + index.ToString() + " in " + GetType().FullName, ex);
}
}
}
///
/// Add an item to the .
///
///
///
/// The to add to the .
///
///
///
/// The position into which the new element was inserted.
///
///
///
/// The is read-only OR...
/// The has a fixed size.
///
///
public int Add(object value)
{
try
{
throw new NotSupportedException("operation is not supported for read-only objects");
}
catch (NotSupportedException ex)
{
throw ex;
}
catch (System.Exception ex)
{
throw new Exception("encountered problem adding item to " + GetType().FullName, ex);
}
}
///
/// Remove all items from the .
///
///
///
/// The is read-only.
///
///
public void Clear()
{
try
{
// TODO: Add some status notification to this thing.
for (ulong i = 0; i < LargeCount; i++)
this[i] = DatumClearValue;
}
catch (NotSupportedException)
{
throw;
}
catch (System.Exception ex)
{
throw new Exception("encountered problem clearing " + GetType().FullName, ex);
}
}
///
/// Determines whether the contains a specific value.
///
///
///
/// The to locate in the .
///
///
///
/// true if the is found in the ;
/// false otherwise.
///
///
public bool Contains(object value)
{
try
{
var itemFound = false;
for (ulong i = 0; i < LargeCount && !itemFound; i++)
if (this[i].Equals(value))
itemFound = true;
return itemFound;
}
catch (System.Exception ex)
{
throw new Exception("encountered problem determining whether " + GetType().FullName + " contains the value: " + value.ToString(), ex);
}
}
///
/// Determine the index of a specific item in the .
///
///
///
/// The to be located in the .
///
///
///
/// the index value if found in the list; -1 otherwise.
///
///
public int IndexOf(object value)
{
try
{
var index = -1;
var largeIndex = LargeIndexOf(value);
if (null == largeIndex)
index = -1;
else if (largeIndex > int.MaxValue)
throw new LargeOverflowException("index of object is to large to be represented by an int (" + largeIndex.ToString() + ")");
else index = (int)largeIndex;
return index;
}
catch (System.Exception ex)
{
throw new Exception("encountered problem determining the index of object \"" + value.ToString() + "\" in " + GetType().FullName, ex);
}
}
///
/// Insert an item into the at the specified index.
///
///
///
/// The zero-based index at which the value should be inserted.
///
///
///
/// The to insert into the .
///
///
///
/// The index is not a valid index in the
///
///
///
/// The is read-only OR...
/// The has a fixed size.
///
///
public void Insert(int index, object value)
{
try
{
throw new NotSupportedException("operation is not supported for read-only objects");
}
catch (ArgumentOutOfRangeException)
{
throw;
}
catch (NotSupportedException)
{
throw;
}
catch (System.Exception ex)
{
throw new Exception("encountered problem inserting item \"" + value.ToString() + "\" into " + GetType().FullName + " at index " + index.ToString(), ex);
}
}
///
/// Remove the first occurance of a specific object from the .
///
///
///
/// The to be removed from the
///
///
///
/// The is read-only OR...
/// the has a fixed size.
///
///
public void Remove(object value)
{
try
{
throw new NotSupportedException("operation is not supported for read-only objects");
}
catch (NotSupportedException ex)
{
throw new Exception("encountered problme removing first occurance of object " + value.ToString() + " from " + GetType().FullName, ex);
}
}
///
/// Remove the item at the specified index.
///
///
///
/// The zero-based index of the item to remove.
///
///
///
/// The index is not a valid index in the .
///
///
///
/// The is read-only OR...
/// the has a fixed size.
///
///
public void RemoveAt(int index)
{
try
{
throw new NotSupportedException("operation is not supported for read-only objects");
}
catch (NotSupportedException)
{
throw;
}
catch (ArgumentOutOfRangeException)
{
throw;
}
catch (System.Exception ex)
{
throw new Exception("encountered problem removing the " + GetType().FullName + " item at index " + index.ToString(), ex);
}
}
// *************************************************************
// ******************** END IList Interface ********************
// *************************************************************
#endregion
#region "Large" ICollection Interface
// *****************************************************************************
// ******************** BEGIN "Large" ICollection Interface ********************
// *****************************************************************************
///
/// Gets the number of elements contained in the .
///
///
public ulong LargeCount
{
get
{
try
{
return Size;
}
catch (System.Exception ex)
{
throw new Exception("encountered problem getting item count for " + GetType().FullName, ex);
}
}
}
///
/// Copies the elements of the to a ,
/// starting at a particular index. If the array is too small
/// (we're storing more than max int items) then an exception will be thrown.
///
///
///
/// The one-dimensional that is the destination of the elements
/// copied from System.Collections.ICollection. The must have zero-based
/// indexing.
///
///
///
/// The zero-based index inn the array at which copying begins.
///
///
///
/// The array is null.
///
///
///
/// The array is multidimensional OR...
/// the index is equal to or greater than the length of the array OR...
/// the number of elements i nthe source System.Collections.ICollection is greater than the available
/// space from index to the end of the destination array.
///
///
///
/// The type of the source System.Collections.ICollection cannot be cast automatically
/// to the type of the destination array.
///
///
public void LargeCopyTo(Array array, ulong index)
{
try
{
if (null == array)
throw new ArgumentNullException("cannot copy to null array reference");
else if (index < 0)
throw new ArgumentOutOfRangeException("cannot copy from negative index (" + index.ToString() + ")");
else if (array.Rank > 1)
throw new ArgumentException("cannot copy to multidimensional array (parameter array rank is " + array.Rank.ToString() + ")");
else if (((ulong)(array.Length) - index) < LargeCount)
throw new ArgumentException("target array is too small (" + ((ulong)(array.Length) - index).ToString() + " < " + LargeCount.ToString() + ")");
else
{
ulong source_index;
ulong target_index;
for (source_index = 0, target_index = index;
source_index < LargeCount;
target_index++, source_index++)
{
((object[])array)[target_index] = this[source_index];
}
}
}
catch (ArgumentNullException)
{
throw;
}
catch (ArgumentOutOfRangeException)
{
throw;
}
catch (ArgumentException)
{
throw;
}
catch (System.Exception ex)
{
throw new Exception("encountered problem copying to " + GetType().FullName, ex);
}
}
// ***************************************************************************
// ******************** END "Large" ICollection Interface ********************
// ***************************************************************************
#endregion
#region "Large" IList Interface
// ***********************************************************************
// ******************** BEGIN "Large" IList Interface ********************
// ***********************************************************************
///
/// Get/set the element at the specified index.
///
///
///
/// The zero-based index of the element to gat or sat.
///
///
///
/// The element at the specified index.
///
///
///
/// The index is not a valid index in the .
///
///
///
/// The property is not set and the is read-only.
///
///
public T this[ulong index]
{
get
{
try
{
return GetDatumAtIndex(index);
}
catch (ArgumentOutOfRangeException)
{
throw;
}
catch (NotSupportedException)
{
throw;
}
catch (System.Exception ex)
{
throw new Exception("encountered problem getting item at index " + index.ToString() + " in " + GetType().FullName, ex);
}
}
set
{
try
{
SetDatumAtIndex(value, index);
}
catch (ArgumentOutOfRangeException)
{
throw;
}
catch (NotSupportedException)
{
throw;
}
catch (System.Exception ex)
{
throw new Exception("encountered problem setting item at index " + index.ToString() + " in " + GetType().FullName, ex);
}
}
}
///
/// Add an item to the .
///
///
///
/// The to add to the .
///
///
///
/// The position into which the new element was inserted.
///
///
///
/// The is read-only OR...
/// The has a fixed size.
///
///
public ulong LargeAdd(object value)
{
try
{
throw new NotSupportedException("operation is not supported for read-only objects");
}
catch (NotSupportedException ex)
{
throw ex;
}
catch (System.Exception ex)
{
throw new Exception("encountered problem adding item to " + GetType().FullName, ex);
}
}
///
/// Determine the index of a specific item in the .
///
///
///
/// The to be located in the .
///
///
///
/// the index value if found in the list; null otherwise.
///
///
public ulong? LargeIndexOf(object value)
{
try
{
ulong? objectIndex = null;
for (ulong i = 0; i < LargeCount && null == objectIndex; i++)
if (this[i].Equals(value))
objectIndex = i;
return objectIndex;
}
catch (System.Exception ex)
{
throw new Exception("encountered problem determining the index of object \"" + value.ToString() + "\" in " + GetType().FullName, ex);
}
}
///
/// Insert an item into the at the specified index.
///
///
///
/// The zero-based index at which the value should be inserted.
///
///
///
/// The to insert into the .
///
///
///
/// The index is not a valid index in the
///
///
///
/// The is read-only OR...
/// The has a fixed size.
///
///
public void LargeInsert(ulong index, object value)
{
try
{
throw new NotSupportedException("operation is not supported for read-only objects");
}
catch (ArgumentOutOfRangeException ex)
{
throw ex;
}
catch (NotSupportedException ex)
{
throw ex;
}
catch (System.Exception ex)
{
throw new Exception("encountered problem inserting item \"" + value.ToString() + "\" into " + GetType().FullName + " at index " + index.ToString(), ex);
}
}
///
/// Remove the item at the specified index.
///
///
///
/// The zero-based index of the item to remove.
///
///
///
/// The index is not a valid index in the .
///
///
///
/// The is read-only OR...
/// the has a fixed size.
///
///
public void LargeRemoveAt(ulong index)
{
try
{
throw new NotSupportedException("operation is not supported for read-only objects");
}
catch (NotSupportedException)
{
throw;
}
catch (ArgumentOutOfRangeException)
{
throw;
}
catch (System.Exception ex)
{
throw new Exception("encountered problem removing the " + GetType().FullName + " item at index " + index.ToString(), ex);
}
}
// *********************************************************************
// ******************** END "Large" IList Interface ********************
// *********************************************************************
#endregion
#region ICloneable Interface
// ********************************************************************
// ******************** BEGIN ICloneable Interface ********************
// ********************************************************************
///
/// create a new object that is a copy of the current instance
///
/// a new object of the same type with all objects duplicated or referenced
public abstract object Clone();
// ******************************************************************
// ******************** END ICloneable Interface ********************
// ******************************************************************
#endregion
}
}