Thread-Safe collection with no order and no duplicates - c#

I need a thread-safe collection to hold items without duplicates. ConcurrentBag<T> allows non-unique items and HashSet<T> is not thread-safe. Is there a collection like this in .NET Framework 4.5?

I suggest you use a ConcurrentDictionary and just use a dummy value for each entry. It's annoying in terms of efficiency (having all those dummy values) but I suspect in the majority of applications that would be insignificant.
You may well want to wrap this in your own ConcurrentSet implementation, which just does enough for your purposes, so that you don't need to see the abstraction leakiness in much of your code.

Here's some code I just wrote that does this ConcurrentSet construct:
public class ConcurrentSet<T> : IEnumerable<T>, ISet<T>, ICollection<T>
{
private readonly ConcurrentDictionary<T, byte> _dictionary = new ConcurrentDictionary<T, byte>();
/// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>
/// <returns>
/// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
/// </returns>
public IEnumerator<T> GetEnumerator()
{
return _dictionary.Keys.GetEnumerator();
}
/// <summary>
/// Returns an enumerator that iterates through a collection.
/// </summary>
/// <returns>
/// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
/// </returns>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
/// <summary>
/// Removes the first occurrence of a specific object from the <see cref="T:System.Collections.Generic.ICollection`1"/>.
/// </summary>
/// <returns>
/// true if <paramref name="item"/> was successfully removed from the <see cref="T:System.Collections.Generic.ICollection`1"/>; otherwise, false. This method also returns false if <paramref name="item"/> is not found in the original <see cref="T:System.Collections.Generic.ICollection`1"/>.
/// </returns>
/// <param name="item">The object to remove from the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param><exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.</exception>
public bool Remove(T item)
{
return TryRemove(item);
}
/// <summary>
/// Gets the number of elements in the set.
/// </summary>
public int Count
{
get { return _dictionary.Count; }
}
/// <summary>
/// Gets a value indicating whether the <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.
/// </summary>
/// <returns>
/// true if the <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only; otherwise, false.
/// </returns>
public bool IsReadOnly { get { return false; } }
/// <summary>
/// Gets a value that indicates if the set is empty.
/// </summary>
public bool IsEmpty
{
get { return _dictionary.IsEmpty; }
}
public ICollection<T> Values
{
get { return _dictionary.Keys; }
}
/// <summary>
/// Adds an item to the <see cref="T:System.Collections.Generic.ICollection`1"/>.
/// </summary>
/// <param name="item">The object to add to the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param><exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.</exception>
void ICollection<T>.Add(T item)
{
if(!Add(item))
throw new ArgumentException("Item already exists in set.");
}
/// <summary>
/// Modifies the current set so that it contains all elements that are present in both the current set and in the specified collection.
/// </summary>
/// <param name="other">The collection to compare to the current set.</param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
public void UnionWith(IEnumerable<T> other)
{
foreach (var item in other)
TryAdd(item);
}
/// <summary>
/// Modifies the current set so that it contains only elements that are also in a specified collection.
/// </summary>
/// <param name="other">The collection to compare to the current set.</param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
public void IntersectWith(IEnumerable<T> other)
{
var enumerable = other as IList<T> ?? other.ToArray();
foreach (var item in this)
{
if (!enumerable.Contains(item))
TryRemove(item);
}
}
/// <summary>
/// Removes all elements in the specified collection from the current set.
/// </summary>
/// <param name="other">The collection of items to remove from the set.</param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
public void ExceptWith(IEnumerable<T> other)
{
foreach (var item in other)
TryRemove(item);
}
/// <summary>
/// Modifies the current set so that it contains only elements that are present either in the current set or in the specified collection, but not both.
/// </summary>
/// <param name="other">The collection to compare to the current set.</param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
public void SymmetricExceptWith(IEnumerable<T> other)
{
throw new NotImplementedException();
}
/// <summary>
/// Determines whether a set is a subset of a specified collection.
/// </summary>
/// <returns>
/// true if the current set is a subset of <paramref name="other"/>; otherwise, false.
/// </returns>
/// <param name="other">The collection to compare to the current set.</param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
public bool IsSubsetOf(IEnumerable<T> other)
{
var enumerable = other as IList<T> ?? other.ToArray();
return this.AsParallel().All(enumerable.Contains);
}
/// <summary>
/// Determines whether the current set is a superset of a specified collection.
/// </summary>
/// <returns>
/// true if the current set is a superset of <paramref name="other"/>; otherwise, false.
/// </returns>
/// <param name="other">The collection to compare to the current set.</param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
public bool IsSupersetOf(IEnumerable<T> other)
{
return other.AsParallel().All(Contains);
}
/// <summary>
/// Determines whether the current set is a correct superset of a specified collection.
/// </summary>
/// <returns>
/// true if the <see cref="T:System.Collections.Generic.ISet`1"/> object is a correct superset of <paramref name="other"/>; otherwise, false.
/// </returns>
/// <param name="other">The collection to compare to the current set. </param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
public bool IsProperSupersetOf(IEnumerable<T> other)
{
var enumerable = other as IList<T> ?? other.ToArray();
return this.Count != enumerable.Count && IsSupersetOf(enumerable);
}
/// <summary>
/// Determines whether the current set is a property (strict) subset of a specified collection.
/// </summary>
/// <returns>
/// true if the current set is a correct subset of <paramref name="other"/>; otherwise, false.
/// </returns>
/// <param name="other">The collection to compare to the current set.</param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
public bool IsProperSubsetOf(IEnumerable<T> other)
{
var enumerable = other as IList<T> ?? other.ToArray();
return Count != enumerable.Count && IsSubsetOf(enumerable);
}
/// <summary>
/// Determines whether the current set overlaps with the specified collection.
/// </summary>
/// <returns>
/// true if the current set and <paramref name="other"/> share at least one common element; otherwise, false.
/// </returns>
/// <param name="other">The collection to compare to the current set.</param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
public bool Overlaps(IEnumerable<T> other)
{
return other.AsParallel().Any(Contains);
}
/// <summary>
/// Determines whether the current set and the specified collection contain the same elements.
/// </summary>
/// <returns>
/// true if the current set is equal to <paramref name="other"/>; otherwise, false.
/// </returns>
/// <param name="other">The collection to compare to the current set.</param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
public bool SetEquals(IEnumerable<T> other)
{
var enumerable = other as IList<T> ?? other.ToArray();
return Count == enumerable.Count && enumerable.AsParallel().All(Contains);
}
/// <summary>
/// Adds an element to the current set and returns a value to indicate if the element was successfully added.
/// </summary>
/// <returns>
/// true if the element is added to the set; false if the element is already in the set.
/// </returns>
/// <param name="item">The element to add to the set.</param>
public bool Add(T item)
{
return TryAdd(item);
}
public void Clear()
{
_dictionary.Clear();
}
public bool Contains(T item)
{
return _dictionary.ContainsKey(item);
}
/// <summary>
/// Copies the elements of the <see cref="T:System.Collections.Generic.ICollection`1"/> to an <see cref="T:System.Array"/>, starting at a particular <see cref="T:System.Array"/> index.
/// </summary>
/// <param name="array">The one-dimensional <see cref="T:System.Array"/> that is the destination of the elements copied from <see cref="T:System.Collections.Generic.ICollection`1"/>. The <see cref="T:System.Array"/> must have zero-based indexing.</param><param name="arrayIndex">The zero-based index in <paramref name="array"/> at which copying begins.</param><exception cref="T:System.ArgumentNullException"><paramref name="array"/> is null.</exception><exception cref="T:System.ArgumentOutOfRangeException"><paramref name="arrayIndex"/> is less than 0.</exception><exception cref="T:System.ArgumentException"><paramref name="array"/> is multidimensional.-or-The number of elements in the source <see cref="T:System.Collections.Generic.ICollection`1"/> is greater than the available space from <paramref name="arrayIndex"/> to the end of the destination <paramref name="array"/>.-or-Type <paramref name="T"/> cannot be cast automatically to the type of the destination <paramref name="array"/>.</exception>
public void CopyTo(T[] array, int arrayIndex)
{
Values.CopyTo(array, arrayIndex);
}
public T[] ToArray()
{
return _dictionary.Keys.ToArray();
}
public bool TryAdd(T item)
{
return _dictionary.TryAdd(item, default(byte));
}
public bool TryRemove(T item)
{
byte donotcare;
return _dictionary.TryRemove(item, out donotcare);
}
}

Related

How to combine partially pre-cached MemoryStream with FileStream?

For a time-critical media presentation application, it is important that media files be presented right at the instance when the user selects it. Those files reside in a truly humongous directory structure, comprised of thousands of media files.
Clearly, caching the media files in a MemoryStream is the way to go; however, due to the sheer amount of files, it’s not feasible to cache each file entirely. Instead, my idea is to pre-cache a certain buffer of each file, and once the file is presented, play from that cache until the rest of the file is loaded from the hard disk.
What I don’t see is how to “concatenate” both the MemoryStream and the FileStream so as to provide a seamless playback experience. I’m not very strong in data streams (yet), and I see several problems:
How does one keep track of the current read position within the MemoryStream and provide that to the FileStream without the MemoryStream reading more than that?
How does one switch from one stream to the other without having both streams either partially overlap each other or creating a “playback break”?
If using a queue of streams (as suggested in How do I concatenate two System.Io.Stream instances into one?), how can I specify that the second stream has to be ready for read access instantaneously after the first stream is done? Here, in particular, I don’t see how the MemoryStream would help at all, since the FileStream, as second one in the queue, would only begin accessing the hard disk once it’s actually used.
Is it really a feasible approach to have literally hundreds, if not thousands of open streams at once?
Note that I don’t need write access—reading is fully sufficient for the problem at hand. Also, this question is similar to Composite Stream Wrapper providing partial MemoryStream and full original Stream, but the solution provided there is a bug fix for Windows Phone 8 that doesn’t apply in my case.
I’d very much like to widen my rather limited understanding of this, so any help is greatly appreciated.
I would suggest something like the following solution:
Inherit your own CachableFileStream from FileStream
Implement a very simple Cache which uses a data structure you prefer (like a Queue)
Allow Preloading data into the internal cache
Allow Reloading data into the internal cache
Modify the original Read behaviour in a way, that your cache is used
To give you an idea of my idea I would suggest some implementation like the following one:
The usage could be like that:
CachableFileStream cachedStream = new CachableFileStream(...)
{
PreloadSize = 8192,
ReloadSize = 4096,
};
// Force preloading data into the cache
cachedStream.Preload();
...
cachedStream.Read(buffer, 0, buffer.Length);
...
Warning: The code below is neither correctly tested nor ideal - this shall just give you an idea!
The CachableFileStream class:
using System;
using System.IO;
using System.Threading.Tasks;
/// <summary>
/// Represents a filestream with cache.
/// </summary>
public class CachableFileStream : FileStream
{
private Cache<byte> cache;
private int preloadSize;
private int reloadSize;
/// <summary>
/// Gets or sets the amount of bytes to be preloaded.
/// </summary>
public int PreloadSize
{
get
{
return this.preloadSize;
}
set
{
if (value <= 0)
throw new ArgumentOutOfRangeException(nameof(value), "The specified preload size must not be smaller than or equal to zero.");
this.preloadSize = value;
}
}
/// <summary>
/// Gets or sets the amount of bytes to be reloaded.
/// </summary>
public int ReloadSize
{
get
{
return this.reloadSize;
}
set
{
if (value <= 0)
throw new ArgumentOutOfRangeException(nameof(value), "The specified reload size must not be smaller than or equal to zero.");
this.reloadSize = value;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="CachableFileStream"/> class with the specified path and creation mode.
/// </summary>
/// <param name="path">A relative or absolute path for the file that the current CachableFileStream object will encapsulate</param>
/// <param name="mode">A constant that determines how to open or create the file.</param>
/// <exception cref="System.ArgumentException">
/// Path is an empty string (""), contains only white space, or contains one or more invalid characters.
/// -or- path refers to a non-file device, such as "con:", "com1:", "lpt1:", etc. in an NTFS environment.
/// </exception>
/// <exception cref="System.NotSupportedException">
/// Path refers to a non-file device, such as "con:", "com1:", "lpt1:", etc. in a non-NTFS environment.
/// </exception>
/// <exception cref="System.ArgumentNullException">
/// Path is null.
/// </exception>
/// <exception cref="System.Security.SecurityException">
/// The caller does not have the required permission.
/// </exception>
/// <exception cref="System.IO.FileNotFoundException">
/// The file cannot be found, such as when mode is FileMode.Truncate or FileMode.Open, and the file specified by path does not exist.
/// The file must already exist in these modes.
/// </exception>
/// <exception cref="System.IO.IOException">
/// An I/O error, such as specifying FileMode.CreateNew when the file specified by path already exists, occurred.-or-The stream has been closed.
/// </exception>
/// <exception cref="System.IO.DirectoryNotFoundException">
/// The specified path is invalid, such as being on an unmapped drive.
/// </exception>
/// <exception cref="System.IO.PathTooLongException">
/// The specified path, file name, or both exceed the system-defined maximum length.
/// For example, on Windows-based platforms, paths must be less than 248 characters, and file names must be less than 260 characters.
/// </exception>
/// <exception cref="System.ArgumentOutOfRangeException">
/// Mode contains an invalid value
/// </exception>
public CachableFileStream(string path, FileMode mode) : base(path, mode)
{
this.cache = new Cache<byte>();
this.cache.CacheIsRunningLow += CacheIsRunningLow;
}
/// <summary>
/// Reads a block of bytes from the stream and writes the data in a given buffer.
/// </summary>
/// <param name="array">
/// When this method returns, contains the specified byte array with the values between
/// offset and (offset + count - 1) replaced by the bytes read from the current source.
/// </param>
/// <param name="offset">The byte offset in array at which the read bytes will be placed.</param>
/// <param name="count">The maximum number of bytes to read.</param>
/// <returns>
/// The total number of bytes read into the buffer. This might be less than the number
/// of bytes requested if that number of bytes are not currently available, or zero
/// if the end of the stream is reached.
/// </returns>
/// <exception cref="System.ArgumentNullException">
/// Array is null.
/// </exception>
/// <exception cref="System.ArgumentOutOfRangeException">
/// Offset or count is negative.
/// </exception>
/// <exception cref="System.NotSupportedException">
/// The stream does not support reading.
/// </exception>
/// <exception cref="System.IO.IOException">
/// An I/O error occurred.
/// </exception>
/// <exception cref="System.ArgumentException">
/// Offset and count describe an invalid range in array.
/// </exception>
/// <exception cref="System.ObjectDisposedException">
/// Methods were called after the stream was closed.
/// </exception>
public override int Read(byte[] array, int offset, int count)
{
int readBytesFromCache;
for (readBytesFromCache = 0; readBytesFromCache < count; readBytesFromCache++)
{
if (this.cache.Size == 0)
break;
array[offset + readBytesFromCache] = this.cache.Read();
}
if (readBytesFromCache < count)
readBytesFromCache += base.Read(array, offset + readBytesFromCache, count - readBytesFromCache);
return readBytesFromCache;
}
/// <summary>
/// Preload data into the cache.
/// </summary>
public void Preload()
{
this.LoadBytesFromStreamIntoCache(this.PreloadSize);
}
/// <summary>
/// Reload data into the cache.
/// </summary>
public void Reload()
{
this.LoadBytesFromStreamIntoCache(this.ReloadSize);
}
/// <summary>
/// Loads bytes from the stream into the cache.
/// </summary>
/// <param name="count">The number of bytes to read.</param>
private void LoadBytesFromStreamIntoCache(int count)
{
byte[] buffer = new byte[count];
int readBytes = base.Read(buffer, 0, buffer.Length);
this.cache.AddRange(buffer, 0, readBytes);
}
/// <summary>
/// Represents the event handler for the CacheIsRunningLow event.
/// </summary>
/// <param name="sender">The sender of the event.</param>
/// <param name="e">Event arguments.</param>
private void CacheIsRunningLow(object sender, EventArgs e)
{
this.cache.WarnIfRunningLow = false;
new Task(() =>
{
Reload();
this.cache.WarnIfRunningLow = true;
}).Start();
}
}
The Cache class:
using System;
using System.Collections.Concurrent;
/// <summary>
/// Represents a generic cache.
/// </summary>
/// <typeparam name="T">Defines the type of the items in the cache.</typeparam>
public class Cache<T>
{
private ConcurrentQueue<T> queue;
/// <summary>
/// Is executed when the number of items within the cache run below the
/// specified warning limit and WarnIfRunningLow is set.
/// </summary>
public event EventHandler CacheIsRunningLow;
/// <summary>
/// Gets or sets a value indicating whether the CacheIsRunningLow event shall be fired or not.
/// </summary>
public bool WarnIfRunningLow
{
get;
set;
}
/// <summary>
/// Gets or sets a value that represents the lower warning limit.
/// </summary>
public int LowerWarningLimit
{
get;
set;
}
/// <summary>
/// Gets the number of items currently stored in the cache.
/// </summary>
public int Size
{
get;
private set;
}
/// <summary>
/// Initializes a new instance of the <see cref="Cache{T}"/> class.
/// </summary>
public Cache()
{
this.queue = new ConcurrentQueue<T>();
this.Size = 0;
this.LowerWarningLimit = 1024;
this.WarnIfRunningLow = true;
}
/// <summary>
/// Adds an item into the cache.
/// </summary>
/// <param name="item">The item to be added to the cache.</param>
public void Add(T item)
{
this.queue.Enqueue(item);
this.Size++;
}
/// <summary>
/// Adds the items of the specified array to the end of the cache.
/// </summary>
/// <param name="items">The items to be added.</param>
public void AddRange(T[] items)
{
this.AddRange(items, 0, items.Length);
}
/// <summary>
/// Adds the specified count of items of the specified array starting
/// from offset to the end of the cache.
/// </summary>
/// <param name="items">The array that contains the items.</param>
/// <param name="offset">The offset that shall be used.</param>
/// <param name="count">The number of items that shall be added.</param>
public void AddRange(T[] items, int offset, int count)
{
for (int i = offset; i < count; i++)
this.Add(items[i]);
}
/// <summary>
/// Reads one item from the cache.
/// </summary>
/// <returns>The item that has been read from the cache.</returns>
/// <exception cref="System.InvalidOperationException">
/// The cache is empty.
/// </exception>
public T Read()
{
T item;
if (!this.queue.TryDequeue(out item))
throw new InvalidOperationException("The cache is empty.");
this.Size--;
if (this.WarnIfRunningLow &&
this.Size < this.LowerWarningLimit)
{
this.CacheIsRunningLow?.Invoke(this, EventArgs.Empty);
}
return item;
}
/// <summary>
/// Peeks the next item from cache.
/// </summary>
/// <returns>The item that has been read from the cache (without deletion).</returns>
/// <exception cref="System.InvalidOperationException">
/// The cache is empty.
/// </exception>
public T Peek()
{
T item;
if (!this.queue.TryPeek(out item))
throw new InvalidOperationException("The cache is empty.");
return item;
}
}
I hope this helps, have fun ;-)

inheritdoc specific exception using cref

How to use <inheritdoc/> to inherit specific exception from another method. Is it possible to do something like this:
/// <summary>
/// Summary
/// </summary>
/// <inheritdoc cref="Method2(int)" select="exception[#cref='CustomException']" />
public int Method1()
{
return Method2(1);
}
/// <summary>
/// Summary...
/// </summary>
/// <exception cref="ArgumentException">
/// ArgumentException...
/// </exception>
/// <exception cref="CustomException">
/// CustomException...
/// </exception>
public int Method2(int count)
{
// do something
}
When I build documentation and open it, there is no exception for Method1. Currently, my workaround is to add id="CustomException" to Method2 and use <inheritdoc cref="Method2(int)" select="exception[#id='CustomException']" />

Nullable Types as Generics

I'm trying to create a linked list for my personal library that can handle EVERYTHING. I'm trying to write it so that it can 'equally' easily hand int, null,DateTime or Class and I wanted it to be easily extendable, so that if I wanted to quickly make stack out of it, I can just write push, pop, and peek methods and so forth.
Currently, my code looks like this. Note that I use 'Base' as my generic type.
namespace ClassLibrary1
{
public class LinkedList<Base> where Base : class
{
public class Node
{
private Node next;
private Node prev;
private Base value;
/// <summary>
/// Constructor for Nodes of Circular Linked List class.
/// Calls overloaded constructor for no previous or next provided.
/// O(1)
/// </summary>
/// <param name="value">The value to be stored. Can use tuple for associations</param>
public Node(Base value)
{
new Node(null, null, value);
}
/// <summary>
/// Constructor for nodes of Circular Linked List class.
/// O(1)
/// </summary>
/// <param name="prev">The previous node in the linked list</param>
/// <param name="next">The next node in the linked list</param>
/// <param name="value">The value to be stored</param>
public Node(Node prev, Node next, Base value)
{
this.prev = prev;
this.next = next;
this.value = value;
}
/// <summary>
/// Sets the 'next' attribute of the node to the passed value.
/// O(1)
/// Chainable
/// </summary>
/// <param name="next">The new value of the 'next' attribute.</param>
/// <returns>Chainable(Node, this)</returns>
public Node setNext(Node next)
{
this.next = next;
return this;
}
/// <summary>
/// Sets the 'prev' attribute of the node to the passed value
/// O(1)
/// Chainable
/// </summary>
/// <param name="prev">The new value of the 'prev' attribute to denote the previous node</param>
/// <returns>Chainable(Node, this)</returns>
public Node setPrev(Node prev)
{
this.prev = prev;
return this;
}
/// <summary>
/// Changes the stored value of type Base to the passed value.
/// O(1)
/// Chainable
/// </summary>
/// <param name="value">The new value to be stored with the node</param>
/// <returns>Chainable(Node, this)</returns>
public Node setVal(Base value)
{
this.value = value;
return this;
}
/// <summary>
/// Returns the next node in the linked list.
/// O(1)
/// </summary>
/// <returns>The next node in the linked list.(Node)</returns>
public Node getNext()
{
return this.next;
}
/// <summary>
/// Returns the previous node in the linked list.
/// O(1)
/// </summary>
/// <returns>The previous node in the linked list.(Node)</returns>
public Node getPrev()
{
return this.prev;
}
/// <summary>
/// Returns the value stored at this node.
/// O(1)
/// </summary>
/// <returns>The value stored at this node.(Base)</returns>
public Base getVal()
{
return this.value;
}
}
public Node head;
public bool duplicates;
public bool hasNullValues;
public bool throwNullError;
/// <summary>
/// Constructor for the LinkedList. Creates a null head node.
/// Duplication defaulted to false
/// O(1)
/// </summary>
public LinkedList()
{
this.head = new Node(null);
this.head.setNext(this.head).setPrev(this.head);
this.duplicates = false;
this.hasNullValues = false;
this.throwNullError = false;
}
/// <summary>
/// Allows duplication for the linked list.
/// O(1)
/// Chainable attribute.
/// </summary>
/// <returns>Chainable.(LinkedList<Base>, this)</returns>
public LinkedList<Base> hasDuplicates()
{
this.duplicates = true;
return this;
}
/// <summary>
/// Allows the structure to store null values in nodes.
/// O(1)
/// Chainable.
/// </summary>
/// <returns>Chainable.(LinkedList<Base>, this)</returns>
public LinkedList<Base> hasNulls()
{
this.hasNullValues = true;
return this;
}
/// <summary>
/// Causes the structure to throw a null error when a null value is inserted.
/// If hasNulls is off, turns it on.
/// O(1)
/// Chainable.
/// </summary>
/// <returns>Chainable.(LinkedList<Base>, this)</returns>
public LinkedList<Base> throwsNulls()
{
if (!this.hasNullValues)
{
this.hasNullValues = true;
}
this.throwNullError = true;
return this;
}
/// <summary>
/// Iff duplicates not allowed, searches for value in list. Throws error if duplicate found.
/// Creates a new node at the end of the list, then links it to the head node.
/// O(length) [if hasDuplicates()]
/// O(1) [if else]
/// Chainable
/// </summary>
/// <param name="value">Value stored at the new node in the list</param>
/// <returns>Chainable.(LinkedList<Base>, this)</returns>
public LinkedList<Base> add(Base value)
{
if (!duplicates)
{
if (search(value) != null)
{
throw new Exception("Value already exists in the linked list.");
}
}
if (!this.hasNullValues && value != null)
{
if (this.throwNullError)
{
throw new Exception("Cannot insert null values");
}
else
{
return this;
}
}
Node newNode = new Node(value);
this.head.getPrev().setNext(newNode);
this.head.setPrev(newNode);
return this;
}
/// <summary>
/// Iterates through the list until first such node for with a matching value is found.
/// Returns null if no matches found.
/// Use searchAll to find duplicates.
/// O(length)
/// </summary>
/// <param name="value">The value to be searched for.</param>
/// <returns>First node with the desired value(Node?)</returns>
public Node search(Base value)
{
Node temp = this.head.getNext();
while (!temp.getVal().Equals(value))
{
if (temp.Equals(this.head))
{
return null;
}
temp = temp.getNext();
}
return temp;
}
/// <summary>
/// If value doesn't exist in the list, throws an exception.
/// Deletes the first node found with the chosen value.
/// Use DeleteAll to delete all instances.
/// Chainable.
/// O(length)
/// </summary>
/// <param name="value">Value to be removed from the list.</param>
/// <returns>Chainable.(LinkedList<Base>, this)</returns>
public LinkedList<Base> delete(Base value)
{
try{
return delete(search(value));
}
catch(Exception e){
throw new Exception("Node to be deleted not found");
}
}
/// <summary>
/// Removes all pointers to the passed node.
/// O(1)
/// </summary>
/// <param name="tbd">The node to be deleted.</param>
/// <returns>Chainable.(LinkedList<Base>, this)</returns>
public LinkedList<Base> delete(Node tbd)
{
if (tbd.Equals(this.head))
{
throw new Exception("Cannot delete head node");
}
else
{
tbd.getPrev().setNext(tbd.getNext());
tbd.getNext().setPrev(tbd.getPrev());
}
return this;
}
/// <summary>
/// Returns a LinkedList of all nodes containing the desired value.
/// O(length)
/// </summary>
/// <param name="value">The value to be found.</param>
/// <returns>A LinkedList of Nodes with matching values.(LinkedList<Node>)</returns>
public LinkedList<Node> searchAll(Base value)
{
LinkedList<Node> returnList = new LinkedList<Node>();
Node temp = this.head.getNext();
while (!temp.Equals(this.head))
{
if (temp.getVal().Equals(value))
{
returnList.add(temp);
}
temp = temp.getNext();
}
return returnList;
}
/// <summary>
/// Returns the first Node in the Linked List.
/// O()
/// </summary>
/// <returns>First non-head node in the list.(Node)</returns>
public Node firstOrDefault()
{
return this.head.getNext();
}
/// <summary>
/// Returns the value of the first node in the list.
/// O(1)
/// </summary>
/// <returns>FIrst non-head </returns>
public Base firstVal()
{
return this.head.getNext().getVal();
}
/// <summary>
/// Gets the last node in the linked list.
/// O(1)
/// </summary>
/// <returns>The last node in the linked list.(Node)</returns>
public Node tail()
{
return this.head.getPrev();
}
/// <summary>
/// Returns the value of the last node in the linked list.
/// O(1)
/// </summary>
/// <returns>VThe value of the tail node.(Base)</returns>
public Base tailVal()
{
return this.head.getPrev().getVal();
}
public static void Main()
{
LinkedLis t<Int32> mine = new LinkedList<Int32>();
}
}
}
However, it gives Red Text under the Int32, saying "The type 'int' must be a reference type in order to use it as a parameter 'Base' in the generic type or method ---this---.
Tell me if you would like me to remove the comments, I'm not sure if that makes it harder or easier to solve.
Because you declared a constraint on a Base type to be a class (a reference type):
public class LinkedList<Base> where Base : class
It exactly forbids using Int32, because it's a value type and is different from a required reference type.
new LinkedList<Int32>()
So, to fix this particular problem, you would need to to create a wrapper class for your integer values.
Before you do this though, check your intentions to store any type in your linked list. Doing so you will strip you off all advantages of C# as a strongly typed language.
And as it was mentioned before, unless you write this code as a pure academic exercise, you should use an existing .NET LinkedList and possibly extend/inherit it, if you need more functionality.
Update: I assumed it went without saying, but to make it crystal clear don't forget that Nullable is a struct, not a class, so you cannot use "cheats" like int?.

Required validation attribute and custom validators order

I have 3 entities.
Let's say I have Event entity, and 2 "derived" entities: Accident, Repair.
They provide some additional fields over Event entity.
Event have StartDate and EndDate which are always required so I mark them with [Required] attribute. That's ok. But I have some additional validation logic that checks if the Event is Repair, then some other Event fields are also required. For this I provide custom validator.
The problem is that the properties marked with [Required] attribute are always checked before other validators.
What I want achieve:
If Event is Accident I want to make Event.SomeField required.
Validation summary should show now contain 3 validation errors at the very first validation attempt.
How it behaves now:
If Event is Accident first validation attempt shows 2 errors of 2 properties marked as [Required]. Only after I fill those, on the next validation attempt fires my custom validator which also states that 3rd Event.SomeField is also required.
I want all the required fields to validate at the same time.
Is this possible? How to achieve this?
For the case that a property is required based on a certain condition, i use a custom attribute, that was initially provided by Jeff Handley in his blog.
Here´s the code of the Attribute:
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = true)]
public class ConditionallyRequiredAttribute : RequiredAttribute {
private MemberInfo _member;
/// <summary>
/// The name of the member that will return the state that indicates
/// whether or not the validated member is required.
/// </summary>
public string ConditionMember { get; private set; }
/// <summary>
/// The condition value under which this validator treats
/// the affected member as required.
/// </summary>
public object RequiredCondition { get; private set; }
/// <summary>
/// Comma-separated list of additional members to
/// add to validation errors. By default, the
/// <see cref="ConditionMember"/> is added.
/// </summary>
public string ErrorMembers { get; set; }
/// <summary>
/// Conditionally require a value, only when the specified
/// <paramref name="conditionMember"/> is <c>true</c>.
/// </summary>
/// <param name="conditionMember">
/// The member that must be <c>true</c> to require a value.
/// </param>
public ConditionallyRequiredAttribute(string conditionMember)
: this(conditionMember, true) { }
/// <summary>
/// Conditionally require a value, only when the specified
/// <paramref name="conditionMember"/> has a value that
/// exactly matches the <paramref name="requiredCondition"/>.
/// </summary>
/// <param name="conditionMember">
/// The member that will be evaluated to require a value.
/// </param>
/// <param name="requiredCondition">
/// The value the <paramref name="conditionMember"/> must
/// hold to require a value.
/// </param>
public ConditionallyRequiredAttribute(string conditionMember, object requiredCondition) {
this.ConditionMember = conditionMember;
this.RequiredCondition = requiredCondition;
this.ErrorMembers = this.ConditionMember;
}
/// <summary>
/// Override the base validation to only perform validation when the required
/// condition has been met. In the case of validation failure, augment the
/// validation result with the <see cref="ErrorMembers"/> as an additional
/// member names, as needed.
/// </summary>
/// <param name="value">The value being validated.</param>
/// <param name="validationContext">The validation context being used.</param>
/// <returns>
/// <see cref="ValidationResult.Success"/> if not currently required or if satisfied,
/// or a <see cref="ValidationResult"/> in the case of failure.
/// </returns>
protected override ValidationResult IsValid(object value, ValidationContext validationContext) {
if (this.DiscoverMember(validationContext.ObjectType)) {
object state = this.InvokeMember(validationContext.ObjectInstance);
// We are only required if the current state
// matches the specified condition.
if (Object.Equals(state, this.RequiredCondition)) {
ValidationResult result = base.IsValid(value, validationContext);
if (result != ValidationResult.Success && this.ErrorMembers != null && this.ErrorMembers.Any()) {
result = new ValidationResult(result.ErrorMessage,
result.MemberNames.Union(this.ErrorMembers.Split(',').Select(s => s.Trim())));
}
return result;
}
return ValidationResult.Success;
}
throw new InvalidOperationException(
"ConditionallyRequiredAttribute could not discover member: " + this.ConditionMember);
}
/// <summary>
/// Discover the member that we will evaluate for checking our condition.
/// </summary>
/// <param name="objectType"></param>
/// <returns></returns>
private bool DiscoverMember(Type objectType) {
if (this._member == null) {
this._member = (from member in objectType.GetMember(this.ConditionMember).Cast<MemberInfo>()
where IsSupportedProperty(member) || IsSupportedMethod(member)
select member).SingleOrDefault();
}
// If we didn't find 1 exact match, indicate that we could not discover the member
return this._member != null;
}
/// <summary>
/// Determine if a <paramref name="member"/> is a
/// method that accepts no parameters.
/// </summary>
/// <param name="member">The member to check.</param>
/// <returns>
/// <c>true</c> if the member is a parameterless method.
/// Otherwise, <c>false</c>.
/// </returns>
private bool IsSupportedMethod(MemberInfo member) {
if (member.MemberType != MemberTypes.Method) {
return false;
}
MethodInfo method = (MethodInfo)member;
return method.GetParameters().Length == 0
&& method.GetGenericArguments().Length == 0
&& method.ReturnType != typeof(void);
}
/// <summary>
/// Determine if a <paramref name="member"/> is a
/// property that has no indexer.
/// </summary>
/// <param name="member">The member to check.</param>
/// <returns>
/// <c>true</c> if the member is a non-indexed property.
/// Otherwise, <c>false</c>.
/// </returns>
private bool IsSupportedProperty(MemberInfo member) {
if (member.MemberType != MemberTypes.Property) {
return false;
}
PropertyInfo property = (PropertyInfo)member;
return property.GetIndexParameters().Length == 0;
}
/// <summary>
/// Invoke the member and return its value.
/// </summary>
/// <param name="objectInstance">The object to invoke against.</param>
/// <returns>The member's return value.</returns>
private object InvokeMember(object objectInstance) {
if (this._member.MemberType == MemberTypes.Method) {
MethodInfo method = (MethodInfo)this._member;
return method.Invoke(objectInstance, null);
}
PropertyInfo property = (PropertyInfo)this._member;
return property.GetValue(objectInstance, null);
}
#if !SILVERLIGHT
/// <summary>
/// The desktop framework has this property and it must be
/// overridden when allowing multiple attributes, so that
/// attribute instances can be disambiguated based on
/// field values.
/// </summary>
public override object TypeId {
get { return this; }
}
#endif
}
Here´s an example:
public class Dummy{
public bool IsCondition {get; set;}
[ConditionallyRequired("IsCondition", true)]
public string SometimesRequired {get; set;}
}

Convert List<T> to object[]

I am looking for a one liner that transforms List<T> into object[]. It's one liner, so I am not interested in solutions such as foreach, or for...
Any takers?
Hint: No, both List<T>.ToArray() and List<T>.ToArray<object>() don't work.
Edit: Why List<T>.ToArray<object>() doesn't work? Because it can't compile.
mylist.Cast<object>().ToArray()
That will only iterate once, by the way, in case you were wondering about the performance. O(n). :)
Why? Well, because Cast<object> will use deferred execution and won't actually do anything until the list is iterated by ToArray().
List<T>.Select(x => x as object).ToArray();
Should return an object[].
If you don't have Linq (.Net 3.0) then you can use the ConvertAll() and ToArray() methods in List:
List<T> list = new List<T>();
object[] objects = list.ConvertAll<object>(item => (object)item).ToArray();
theList.Cast<object>().ToArray()
or
new List<object>(theList).ToArray()
And for a pre-LINQ solution (that only works for reference types).
(object[])List<T>.ToArray();
If you don't mind writing a very short, reusable function, the ConvertAll Extension Method might help:
http://msdn.microsoft.com/en-us/library/73fe8cwf.aspx
EDIT:
This would work too
List<int> intList = new List<int>() { 1, 3, 4 };
object[] objectList = intList.ConvertAll(item => (object)item).ToArray();
In C# on .NET 2.0 (VS 2008) the following compiles and doesn't use LINQ (as far as I can see) for reference types.
object[] oArray;
List<MyObject> oList = new List<MyObject>();
oArray = oList.ToArray();
This does not require a cast as all reference types have object as their base.
I'd suggest creating a ListCastAdapter,
Lets say you want to Convert List to List
Create implementation of
an implementation of IList that return items from a List
most likely i call it class ListCastAdapter
Have a great day
implementation (NOT TESTED):
Notice: It is recomended to make the list ReadOnly,
for the fact, now the user can insert objects that are way up the hierarchy in the original
list.
Note: CopyTo() is not implemented, you can create the same idea for the array.
using System.Collections;
using System.Collections.Generic;
namespace UDF.MyDataLayer
{
internal class ListCastAdapter<S,T> : IList<T> where T : class where S : class
{
private List<S> adaptee;
public ListCastAdapter(List<S> adaptee )
{
this.adaptee = adaptee;
}
#region Implementation of IEnumerable
public class EnumeratorCastAdapter : IEnumerator<T>
{
private IEnumerator<S> adaptee;
public EnumeratorCastAdapter(IEnumerator<S> adaptee)
{
this.adaptee = adaptee;
}
#region Implementation of IDisposable
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
/// <filterpriority>2</filterpriority>
public void Dispose()
{
adaptee.Dispose();
}
#endregion
#region Implementation of IEnumerator
/// <summary>
/// Advances the enumerator to the next element of the collection.
/// </summary>
/// <returns>
/// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.
/// </returns>
/// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created. </exception><filterpriority>2</filterpriority>
public bool MoveNext()
{
return adaptee.MoveNext();
}
/// <summary>
/// Sets the enumerator to its initial position, which is before the first element in the collection.
/// </summary>
/// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created. </exception><filterpriority>2</filterpriority>
public void Reset()
{
adaptee.Reset();
}
/// <summary>
/// Gets the element in the collection at the current position of the enumerator.
/// </summary>
/// <returns>
/// The element in the collection at the current position of the enumerator.
/// </returns>
public T Current
{
get
{
// needs to check if it is an Object or Value Type
return adaptee.Current as T;
}
}
/// <summary>
/// Gets the current element in the collection.
/// </summary>
/// <returns>
/// The current element in the collection.
/// </returns>
/// <exception cref="T:System.InvalidOperationException">The enumerator is positioned before the first element of the collection or after the last element.</exception><filterpriority>2</filterpriority>
object IEnumerator.Current
{
get { return Current; }
}
#endregion
}
/// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>
/// <returns>
/// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
/// </returns>
/// <filterpriority>1</filterpriority>
public IEnumerator<T> GetEnumerator()
{
return new EnumeratorCastAdapter(adaptee.GetEnumerator());
}
/// <summary>
/// Returns an enumerator that iterates through a collection.
/// </summary>
/// <returns>
/// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
/// </returns>
/// <filterpriority>2</filterpriority>
IEnumerator IEnumerable.GetEnumerator()
{
return adaptee.GetEnumerator();
}
#endregion
#region Implementation of ICollection<T>
/// <summary>
/// Adds an item to the <see cref="T:System.Collections.Generic.ICollection`1"/>.
/// </summary>
/// <param name="item">The object to add to the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param><exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.</exception>
public void Add(T item)
{
adaptee.Add(item as S);
}
/// <summary>
/// Removes all items from the <see cref="T:System.Collections.Generic.ICollection`1"/>.
/// </summary>
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only. </exception>
public void Clear()
{
adaptee.Clear();
}
/// <summary>
/// Determines whether the <see cref="T:System.Collections.Generic.ICollection`1"/> contains a specific value.
/// </summary>
/// <returns>
/// true if <paramref name="item"/> is found in the <see cref="T:System.Collections.Generic.ICollection`1"/>; otherwise, false.
/// </returns>
/// <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param>
public bool Contains(T item)
{
return adaptee.Contains(item as S);
}
/// <summary>
/// Copies the elements of the <see cref="T:System.Collections.Generic.ICollection`1"/> to an <see cref="T:System.Array"/>, starting at a particular <see cref="T:System.Array"/> index.
/// </summary>
/// <param name="array">The one-dimensional <see cref="T:System.Array"/> that is the destination of the elements copied from <see cref="T:System.Collections.Generic.ICollection`1"/>. The <see cref="T:System.Array"/> must have zero-based indexing.</param><param name="arrayIndex">The zero-based index in <paramref name="array"/> at which copying begins.</param><exception cref="T:System.ArgumentNullException"><paramref name="array"/> is null.</exception><exception cref="T:System.ArgumentOutOfRangeException"><paramref name="arrayIndex"/> is less than 0.</exception><exception cref="T:System.ArgumentException"><paramref name="array"/> is multidimensional.-or-The number of elements in the source <see cref="T:System.Collections.Generic.ICollection`1"/> is greater than the available space from <paramref name="arrayIndex"/> to the end of the destination <paramref name="array"/>.-or-Type <paramref name="T"/> cannot be cast automatically to the type of the destination <paramref name="array"/>.</exception>
public void CopyTo(T[] array, int arrayIndex)
{
throw new System.NotImplementedException("Not Needed by Me, implement ArrayCastAdapter if needed");
}
/// <summary>
/// Removes the first occurrence of a specific object from the <see cref="T:System.Collections.Generic.ICollection`1"/>.
/// </summary>
/// <returns>
/// true if <paramref name="item"/> was successfully removed from the <see cref="T:System.Collections.Generic.ICollection`1"/>; otherwise, false. This method also returns false if <paramref name="item"/> is not found in the original <see cref="T:System.Collections.Generic.ICollection`1"/>.
/// </returns>
/// <param name="item">The object to remove from the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param><exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.</exception>
public bool Remove(T item)
{
adaptee.Remove(item as S);
}
/// <summary>
/// Gets the number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1"/>.
/// </summary>
/// <returns>
/// The number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1"/>.
/// </returns>
public int Count
{
get { return adaptee.Count; }
}
/// <summary>
/// Gets a value indicating whether the <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.
/// </summary>
/// <returns>
/// true if the <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only; otherwise, false.
/// </returns>
public bool IsReadOnly
{
get
{
return true; // change, to live on the edge
}
}
#endregion
#region Implementation of IList<T>
/// <summary>
/// Determines the index of a specific item in the <see cref="T:System.Collections.Generic.IList`1"/>.
/// </summary>
/// <returns>
/// The index of <paramref name="item"/> if found in the list; otherwise, -1.
/// </returns>
/// <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.IList`1"/>.</param>
public int IndexOf(T item)
{
return adaptee.IndexOf(item as S);
}
/// <summary>
/// Inserts an item to the <see cref="T:System.Collections.Generic.IList`1"/> at the specified index.
/// </summary>
/// <param name="index">The zero-based index at which <paramref name="item"/> should be inserted.</param><param name="item">The object to insert into the <see cref="T:System.Collections.Generic.IList`1"/>.</param><exception cref="T:System.ArgumentOutOfRangeException"><paramref name="index"/> is not a valid index in the <see cref="T:System.Collections.Generic.IList`1"/>.</exception><exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.IList`1"/> is read-only.</exception>
public void Insert(int index, T item)
{
adaptee.Insert(index, item as S);
}
/// <summary>
/// Removes the <see cref="T:System.Collections.Generic.IList`1"/> item at the specified index.
/// </summary>
/// <param name="index">The zero-based index of the item to remove.</param><exception cref="T:System.ArgumentOutOfRangeException"><paramref name="index"/> is not a valid index in the <see cref="T:System.Collections.Generic.IList`1"/>.</exception><exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.IList`1"/> is read-only.</exception>
public void RemoveAt(int index)
{
adaptee.RemoveAt(index);
}
/// <summary>
/// Gets or sets the element at the specified index.
/// </summary>
/// <returns>
/// The element at the specified index.
/// </returns>
/// <param name="index">The zero-based index of the element to get or set.</param><exception cref="T:System.ArgumentOutOfRangeException"><paramref name="index"/> is not a valid index in the <see cref="T:System.Collections.Generic.IList`1"/>.</exception><exception cref="T:System.NotSupportedException">The property is set and the <see cref="T:System.Collections.Generic.IList`1"/> is read-only.</exception>
public T this[int index]
{
get { return adaptee[index] as T; }
set { adaptee[index] = value as S; }
}
#endregion
}
}

Categories

Resources