I'm working with Moles to write some unit tests. I searched online but I don't see any responses on how to use Moles to intercept the calls to AppSettingsReader.GetValue.
Has anyone been able to do this using Moles? Or am I forced into isolating the calls in my own class I can inject or mock? Ideally there is a way to directly use Moles to intercept the calls because we don't really want to modify the code we're looking to put under test.
Thanks!
Firstly, I strongly recommend moving to the release version of Moles, called "Fakes and Stubs", in .NET 4.5 / C# 5 / Visual Studio 2012.
The System.Configurations namespace is incompatible with the Mole/Fake type, and must be stubbed. To create a stub using the Moles Framework, simply create an interface for the System.Configuration.AppSettingsReader type. The Moles compiler will automatically convert the interface into a Stub type.
Here's an interface you can add to your project:
using System;
namespace YOUR_NAMESPACE_HERE
{
/// <summary>
/// IOC object for stubbing System.Configuration.AppSettingsReader.
/// Provides a method for reading values of a particular type from
/// the configuration.
/// </summary>
interface IAppSettingsReader
{
/// <summary>
/// Gets the value for a specified key from the
/// System.Configuration.ConfigurationSettings.AppSettings property
/// and returns an object of the specified type containing the
/// value from the configuration.
/// </summary>
/// <param name="key">The key for which to get the value.</param>
/// <param name="type">The type of the object to return.</param>
/// <returns>The value of the specified key</returns>
/// <exception cref="System.ArgumentNullException">key is null.
/// - or -
/// type is null.</exception>
/// <exception cref="System.InvalidOperationException">key does
/// not exist in the <appSettings> configuration section.
/// - or -
/// The value in the <appSettings> configuration section
/// for key is not of type type.</exception>
public object GetValue(string key, Type type);
}
}
Here's a stub class, too:
using System;
using System.Configuration;
namespace YOUR_NAMESPACE_HERE
{
/// <summary>
/// Production stub for System.Configuration.AppSettingsReader.
/// Provides a method for reading values of a particular type from
/// the configuration.
/// </summary>
public class AppSettingsReaderStub : IAppSettingsReader
{
/// <summary>
/// Gets the value for a specified key from the
/// System.Configuration.ConfigurationSettings.AppSettings property
/// and returns an object of the specified type containing the value
/// from the configuration.
/// </summary>
/// <param name="key">The key for which to get the value.</param>
/// <param name="type">The type of the object to return.</param>
/// <returns>The value of the specified key</returns>
/// <exception cref="System.ArgumentNullException">key is null.
/// - or -
/// type is null.</exception>
/// <exception cref="System.InvalidOperationException">key does not
/// exist in the <appSettings> configuration section.
/// - or -
/// The value in the <appSettings> configuration section for
/// key is not of type type.</exception>
public object GetValue(string key, Type type)
{
var reader = new AppSettingsReader();
object result = reader.GetValue(key, type);
return result;
}
}
}
Related
I know there are questions like this, but they're old. So I'm creating a new one.
At the moment when there are 3 overloaded methods I have to do this:
/// <summary>
/// Description that described summary of an overloaded method.
/// </summary>
/// <param name="fileName">Description that describes filename parameter</param>
/// <param name="options">Description that describes options parameter</param>
/// <returns>Description of what method returns</returns>
public bool ReadFrom(string fileName, ReaderOptions options = null) {
return false;
}
/// <summary>
/// Description that described summary of an overloaded method.
/// </summary>
/// <param name="stream">Description that describes stream parameter</param>
/// <param name="options">Description that describes options parameter</param>
/// <returns>Description of what method returns</returns>
public bool ReadFrom(Stream stream, ReaderOptions options = null) {
return false;
}
/// <summary>
/// Description that described summary of an overloaded method.
/// </summary>
/// <param name="rawData">Description that describes rawData parameter</param>
/// <param name="options">Description that describes options parameter</param>
/// <returns>Description of what method returns</returns>
public bool ReadFrom(byte[] rawData, ReaderOptions options = null) {
return false;
}
And I would like to have something like this:
#region overloadedReadFromMethods
/// <summary>
/// Description that described summary of an overloaded method.
/// </summary>
/// <param name="fileName">Description that describes filename parameter</param>
/// <param name="options">Description that describes options parameter</param>
/// <returns>Description of what method returns</returns>
public bool ReadFrom(string fileName, ReaderOptions options = null) {
return false;
}
/// <param name="stream">Description that describes stream parameter</param>
public bool ReadFrom(Stream stream, ReaderOptions options = null) {
return false;
}
/// <param name="rawData">Description that describes rawData parameter</param>
/// <returns>Even considering that returns tag is present on the first overloaded method,
/// this overloaded method shows this specific description.
/// </returns>
public bool ReadFrom(byte[] rawData, ReaderOptions options = null) {
return false;
}
#endregion overloadedReadFromMethods
So the first overloaded method describes default description and then methods below can override it with their own descriptions. I want it to show in Visual Studio's IntelliSense.
TLDR - It's not possible
Long story short, as was the case in the past, you still cannot re-use comments this way.
Some interesting ideas here
Create one function with optional parameters. While this would mitigate the problem, I find that optional parameters are sometimes incovenient themselves as they overcomplicate the logic inside and make unit testing very difficult. Overaloading in your case make sense, so this solution does not apply.
Use the <overloads> comment. I can't see it in the official documentation though
Use the <see> and <seealso> xml tag to use reference
Use the <include> tag
This is still not a solution but it allows you to have separate xml documents and handle overall. include documentation
I think the extra work to document each method is necessary because they all have different signatures. Methods have different <param></param>
InheritDoc is a package that can be used to inherit xml docs.
How do you reference a type parameter in XML code documentation? For instance, this code
/// <summary>
/// An interface.
/// </summary>
/// <typeparam name="TInterface">Type paramter.</typeparam>
public interface IFace<TInterface>
{
/// <summary>
/// Does the thing.
/// </summary>
/// <typeparam name="TMethod">Different from <see cref="TInterface"/>.</typeparam>
/// <returns>An integer.</returns>
int SomeMethod<TMethod>();
}
Gives a warning about typeparam name="TMethod":
XML comment has cref attribute 'TInterface' that refers to a type paramter.
This question asks about referencing a generic type, but I want to reference the type parameter.
Instead of using see cref, the typeparamref should be used instead:
/// <summary>
/// An interface.
/// </summary>
/// <typeparam name="TInterface">Type paramter.</typeparam>
public interface IFace<TInterface>
{
/// <summary>
/// Does the thing.
/// </summary>
/// <typeparam name="TMethod">Different from <typeparamref name="TInterface"/>.</typeparam>
/// <returns>An integer.</returns>
int SomeMethod<TMethod>();
}
Currently, I am working on IEGL10 in Xamarin. I have implemented ISurfaceHolderCallback and on SurfaceCreated(ISurfaceHolder holder) I have to call a method like this.
public void SurfaceCreated(ISurfaceHolder holder)
{
mEglSurface = mEgl.EglCreateWindowSurface(mEglDisplay, mEglConfig,
holder, null);
}
The problem is, the holder is a C# interface and EglCreateWindowSurface requires Java.Lang.Object. So how can I do the casting. If I directly cast holder like (Java.Lang.Object)holder. It is throwing invalid cast exception.
Please help guys I am really stuck here.
How to cast C# interface to Java.Lang.Object?
MonoDroid has integrated extension for this purpose :
Java.Lang.Object holder_object = holder.JavaCast<Java.Lang.Object>();
EGLSurface mEglSurface = mEgl.EglCreateWindowSurface(mEglDisplay, mEglConfig, holder_object, null);
You could see the document :
public static class Extensions
{
//
// Summary:
// /// Performs an Android runtime-checked type conversion. ///
//
// Parameters:
// instance:
// /// An Android.Runtime.IJavaObject instance to convert /// to a TResult instance.
// ///
//
// Type parameters:
// TResult:
// /// The type to convert instance to. /// TResult must implement the /// Android.Runtime.IJavaObject
// interface. ///
//
// Returns:
// /// A TResult representation for /// instance. ///
//
// Exceptions:
// T:System.ArgumentException:
// ///
// /// The JNI class for TResult cannot be found. ///
// ///
// -or-
// ///
// /// The proxy class for TResult is /// abstract, and the non-abstract Proxy can't
// be found. ///
// ///
//
// T:System.InvalidCastException:
// /// The Anrdroid object instance instance.Handle /// cannot be converted to the
// Android type corresponding to /// TResult. ///
//
// T:System.NotSupportedException:
// /// An unknown error occurred. ///
//
// Remarks:
// /// /// This is a hack, but a currently necessary one. /// ///
// /// Most of the Android types are staticly generated /// wrappers over a description
// of the underlying Android types. This /// intermediate description does not expose
// implementation details, /// which sometimes must be relied upon. ///
// ///
// /// For example, consider the /// Javax.Microedition.Khronos.Egl.EGLContext.EGL
// /// property, which returns an instance of the /// Javax.Microedition.Khronos.Egl.IEGL
// /// interface. This interface is useless, containing no members to /// invoke
// or use. The developer is instead expected to convert this /// instance to an
// interface which contains actual operations, such as /// the Javax.Microedition.Khronos.Egl.IEGL10
// interface. /// Unfortunately, the MonoDroid-generated wrappers do not know this,
// /// nor can they (the EGL10 implementation may be removed in a /// future Android
// version). The result is that if developers attempt /// to cast within managed
// code, the result will be a /// System.InvalidCastException: ///
// /// EGL10 egl10 = (EGL10) EGLContext.EGL; // throws ///
// /// The JavaCast() method allows performing such type conversions /// while bypassing
// the managed type system and instead relying upon /// the Android runtime system
// to perform the type checking. This /// allows: ///
// /// EGL10 egl10 = EGLContext.EGL.JavaCast<EGL10>(); // good ///
public static TResult JavaCast<TResult>(this IJavaObject instance) where TResult : class, IJavaObject;
}
I need to know how to override the Add-method of a certain Dictionary in a certain static class. Any suggestions?
If it matters, the dictionary looks like this:
public static Dictionary<MyEnum,MyArray[]>
Any suggestions?
You can't override the Add method of Dictionary<,> since it's non virtual. You can hide it by adding a method with the same name/signature in the derived class, but hiding isn't the same as overriding. If somebody casts to the base class he will still call the wrong Add.
The correct way to do this is to create your own class that implements IDictionary<,> (the interface) but has a Dictionary<,> (the class) instead of being a Dictionary<,>.
class MyDictionary<TKey,TValue>:IDictionary<TKey,TValue>
{
private Dictionary<TKey,TValue> backingDictionary;
//Implement the interface here
//Delegating most of the logic to your backingDictionary
...
}
I went with CodesInChaos's solution. Since there's a bit of overhead involved in implementing IDictionary<TKey, TValue>, here is a virtual implementation for general consumption. I'm including some comments and an example below for optional reading. Let me know if I should fix or improve something. I haven't done much testing, but it seems to work for overriding the add method.
using System.Collections;
using System.Collections.Generic;
/// <summary>
/// Represents a collection of keys and values. This is an abstract base-class wrapping a <see cref="T:System.Collections.Generic.Dictionary`2"/> with
/// virtual method that can be overridden. This class can be used to override the default functionality of <see cref="T:System.Collections.Generic.Dictionary`2"/>.
/// </summary>
/// <typeparam name="TKey">The type of the keys in the dictionary.</typeparam>
/// <typeparam name="TValue">The type of the values in the dictionary.</typeparam>
public class VirtualDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
protected IDictionary<TKey, TValue> wrappedDictionary;
/// <summary>
/// Initializes a new instance of the <see cref="VirtualDictionary{TKey,TValue}"/> class that is empty, has the default initial capacity, and uses the default equality comparer for the key type.
/// </summary>
public VirtualDictionary()
{
wrappedDictionary = new Dictionary<TKey, TValue>();
}
/// <summary>
/// Initializes a new instance of the <see cref="VirtualDictionary{TKey,TValue}"/> class that is empty, has the specified initial capacity, and uses the default equality comparer for the key type.
/// </summary>
/// <param name="capacity">The initial number of elements that the <see cref="T:System.Collections.Generic.VirtualDictionary`2"/> can contain.</param><exception cref="T:System.ArgumentOutOfRangeException"><paramref name="capacity"/> is less than 0.</exception>
public VirtualDictionary(int capacity)
{
wrappedDictionary = new Dictionary<TKey, TValue>(capacity);
}
/// <summary>
/// Initializes a new instance of the <see cref="VirtualDictionary{TKey,TValue}"/> class that is empty, has the default initial capacity, and uses the specified <see cref="T:System.Collections.Generic.IEqualityComparer`1"/>.
/// </summary>
/// <param name="comparer">The <see cref="T:System.Collections.Generic.IEqualityComparer`1"/> implementation to use when comparing keys, or null to use the default <see cref="T:System.Collections.Generic.EqualityComparer`1"/> for the type of the key.</param>
public VirtualDictionary(IEqualityComparer<TKey> comparer)
{
wrappedDictionary = new Dictionary<TKey, TValue>(comparer);
}
/// <summary>
/// Initializes a new instance of the <see cref="VirtualDictionary{TKey,TValue}"/> class that is empty, has the specified initial capacity, and uses the specified <see cref="T:System.Collections.Generic.IEqualityComparer`1"/>.
/// </summary>
/// <param name="capacity">The initial number of elements that the <see cref="VirtualDictionary{TKey,TValue}"/> can contain.</param><param name="comparer">The <see cref="T:System.Collections.Generic.IEqualityComparer`1"/> implementation to use when comparing keys, or null to use the default <see cref="T:System.Collections.Generic.EqualityComparer`1"/> for the type of the key.</param><exception cref="T:System.ArgumentOutOfRangeException"><paramref name="capacity"/> is less than 0.</exception>
public VirtualDictionary(int capacity, IEqualityComparer<TKey> comparer)
{
wrappedDictionary = new Dictionary<TKey, TValue>(capacity, comparer);
}
/// <summary>
/// Initializes a new instance of the <see cref="VirtualDictionary{TKey,TValue}"/> class that contains elements copied from the specified <see cref="T:System.Collections.Generic.IDictionary`2"/> and uses the default equality comparer for the key type.
/// </summary>
/// <param name="dictionary">The <see cref="T:System.Collections.Generic.IDictionary`2"/> whose elements are copied to the new <see cref="VirtualDictionary{TKey,TValue}"/>.</param><exception cref="T:System.ArgumentNullException"><paramref name="dictionary"/> is null.</exception><exception cref="T:System.ArgumentException"><paramref name="dictionary"/> contains one or more duplicate keys.</exception>
public VirtualDictionary(IDictionary<TKey, TValue> dictionary)
{
wrappedDictionary = new Dictionary<TKey, TValue>(dictionary);
}
/// <summary>
/// Initializes a new instance of the <see cref="VirtualDictionary{TKey,TValue}"/> class that contains elements copied from the specified <see cref="T:System.Collections.Generic.IDictionary`2"/> and uses the specified <see cref="T:System.Collections.Generic.IEqualityComparer`1"/>.
/// </summary>
/// <param name="dictionary">The <see cref="T:System.Collections.Generic.IDictionary`2"/> whose elements are copied to the new <see cref="VirtualDictionary{TKey,TValue}"/>.</param><param name="comparer">The <see cref="T:System.Collections.Generic.IEqualityComparer`1"/> implementation to use when comparing keys, or null to use the default <see cref="T:System.Collections.Generic.EqualityComparer`1"/> for the type of the key.</param><exception cref="T:System.ArgumentNullException"><paramref name="dictionary"/> is null.</exception><exception cref="T:System.ArgumentException"><paramref name="dictionary"/> contains one or more duplicate keys.</exception>
public VirtualDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer)
{
wrappedDictionary = new Dictionary<TKey, TValue>(dictionary, comparer);
}
/// <summary>
/// Adds an element with the provided key and value to the <see cref="T:System.Collections.Generic.IDictionary`2"/>.
/// </summary>
/// <param name="key">The object to use as the key of the element to add.</param><param name="value">The object to use as the value of the element to add.</param><exception cref="T:System.ArgumentNullException"><paramref name="key"/> is null.</exception><exception cref="T:System.ArgumentException">An element with the same key already exists in the <see cref="T:System.Collections.Generic.IDictionary`2"/>.</exception><exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.IDictionary`2"/> is read-only.</exception>
public virtual void Add(TKey key, TValue value)
{
wrappedDictionary.Add(key, value);
}
/// <summary>
/// Determines whether the <see cref="T:System.Collections.Generic.IDictionary`2"/> contains an element with the specified key.
/// </summary>
/// <returns>
/// true if the <see cref="T:System.Collections.Generic.IDictionary`2"/> contains an element with the key; otherwise, false.
/// </returns>
/// <param name="key">The key to locate in the <see cref="T:System.Collections.Generic.IDictionary`2"/>.</param><exception cref="T:System.ArgumentNullException"><paramref name="key"/> is null.</exception>
public virtual bool ContainsKey(TKey key)
{
return wrappedDictionary.ContainsKey(key);
}
/// <summary>
/// Gets an <see cref="T:System.Collections.Generic.ICollection`1"/> containing the keys of the <see cref="T:System.Collections.Generic.IDictionary`2"/>.
/// </summary>
/// <returns>
/// An <see cref="T:System.Collections.Generic.ICollection`1"/> containing the keys of the object that implements <see cref="T:System.Collections.Generic.IDictionary`2"/>.
/// </returns>
public virtual ICollection<TKey> Keys
{
get
{
return wrappedDictionary.Keys;
}
}
/// <summary>
/// Removes the element with the specified key from the <see cref="T:System.Collections.Generic.IDictionary`2"/>.
/// </summary>
/// <returns>
/// true if the element is successfully removed; otherwise, false. This method also returns false if <paramref name="key"/> was not found in the original <see cref="T:System.Collections.Generic.IDictionary`2"/>.
/// </returns>
/// <param name="key">The key of the element to remove.</param><exception cref="T:System.ArgumentNullException"><paramref name="key"/> is null.</exception><exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.IDictionary`2"/> is read-only.</exception>
public virtual bool Remove(TKey key)
{
return wrappedDictionary.Remove(key);
}
/// <summary>
/// Gets the value associated with the specified key.
/// </summary>
/// <returns>
/// true if the object that implements <see cref="T:System.Collections.Generic.IDictionary`2"/> contains an element with the specified key; otherwise, false.
/// </returns>
/// <param name="key">The key whose value to get.</param><param name="value">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 <paramref name="value"/> parameter. This parameter is passed uninitialized.</param><exception cref="T:System.ArgumentNullException"><paramref name="key"/> is null.</exception>
public virtual bool TryGetValue(TKey key, out TValue value)
{
return wrappedDictionary.TryGetValue(key, out value);
}
/// <summary>
/// Gets an <see cref="T:System.Collections.Generic.ICollection`1"/> containing the values in the <see cref="T:System.Collections.Generic.IDictionary`2"/>.
/// </summary>
/// <returns>
/// An <see cref="T:System.Collections.Generic.ICollection`1"/> containing the values in the object that implements <see cref="T:System.Collections.Generic.IDictionary`2"/>.
/// </returns>
public virtual ICollection<TValue> Values
{
get
{
return wrappedDictionary.Values;
}
}
/// <summary>
/// Gets or sets the element with the specified key.
/// </summary>
/// <returns>
/// The element with the specified key.
/// </returns>
/// <param name="key">The key of the element to get or set.</param><exception cref="T:System.ArgumentNullException"><paramref name="key"/> is null.</exception><exception cref="T:System.Collections.Generic.KeyNotFoundException">The property is retrieved and <paramref name="key"/> is not found.</exception><exception cref="T:System.NotSupportedException">The property is set and the <see cref="T:System.Collections.Generic.IDictionary`2"/> is read-only.</exception>
public virtual TValue this[TKey key]
{
get
{
return wrappedDictionary[key];
}
set
{
wrappedDictionary[key] = value;
}
}
/// <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 virtual void Add(KeyValuePair<TKey, TValue> item)
{
wrappedDictionary.Add(item);
}
/// <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 virtual void Clear()
{
wrappedDictionary.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 virtual bool Contains(KeyValuePair<TKey, TValue> item)
{
return wrappedDictionary.Contains(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">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"/>.</exception>
public virtual void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
wrappedDictionary.CopyTo(array, arrayIndex);
}
/// <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 virtual int Count
{
get
{
return wrappedDictionary.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 virtual bool IsReadOnly
{
get { return wrappedDictionary.IsReadOnly; }
}
/// <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 virtual bool Remove(KeyValuePair<TKey, TValue> item)
{
return wrappedDictionary.Remove(item);
}
/// <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 virtual IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return wrappedDictionary.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 GetEnumerator();
}
}
My use case was to be able to run the same code to manipulate a DataTable and a kendo datasource result which is IDictionary. So, I wrapped the DataTable with my custom IDictionary such that updates to the IDictionary were made on the DataRow. Maybe someone will find that useful and should serve as an example implementation.
public class DataRowDictionaryWrapper : VirtualDictionary<string, object>
{
private DataRow row;
public DataRowDictionaryWrapper(DataRow row)
{
this.row = row;
this.wrappedDictionary = row.Table.Columns.Cast<DataColumn>().ToDictionary(key => key.ColumnName, c => row[c]);
}
public override void Add(string key, object value)
{
DataColumn col = row.Table.Columns[key];
if (col == null)
{
col = new DataColumn(key);
row.Table.Columns.Add(col);
}
row[col] = value;
base[key] = value;
}
}
public class DataTableDictionaryWrapper : IEnumerable<IDictionary<string, object>>
{
private DataTable dt;
public DataTableDictionaryWrapper(DataTable dt)
{
this.dt = dt;
}
public IEnumerator<IDictionary<string, object>> GetEnumerator()
{
foreach (DataRow row in dt.Rows)
{
yield return new DataRowDictionaryWrapper(row);
}
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
It's actually simpler to use the "new" keyword. I do this often when my objects contain thier own unique ID value.
// Warning Air Code - Subject to bonehead mistakes
internal sealed class SomeDict : Dictionary<myKeyType, myObjectType>
{
public new void Add(myKeyType key, myObjectType value)
{
this.Add(value);
}
internal void Add(myObjectType value)
{
base.Add(value.Id, value);
}
}
In some scenarios, where TKey is a property of TValue, you may want to consider using KeyedCollection<TKey, TValue>, which allows you to override InsertItem, SetItem, RemoveItem, and ClearItems.
It uses a dictionary internally (unless specified otherwise via the threshold constructor parameter), but also manages a collection.
I Haven't benchmarked or compared performance, there might be a performance difference so use carefully.
Consider voting on this feature suggestion on the .NET Core repo.
Another "quick and dirty" fix
If you need to add some extra logic to the Add method, you can create your own method, with a slightly different signature, and use THAT in your code.
public class MyOwnDictionary<TKey, TValue>: Dictionary<TKey, TValue>
{
public void AddPlus(TKey key, TValue value)
{
//some custom logic here
base.Add(key,value);
}
}
(but this can lead to maintenance difficulties)
I've got an IDictionary field that I would like to expose via a property of type IDictionary<string, dynamic> the conversion is surprisingly difficult since I have no idea what I can .Cast<>() the IDictionary to.
Best I've got:
IDictionary properties;
protected virtual IDictionary<string, dynamic> Properties {
get {
return _properties.Keys.Cast<string>()
.ToDictionary(name=>name, name=> _properties[name] as dynamic);
}
}
If the underlying type of the IDictionary does not implement IDictionary<string, dynamic>, you cannot cast the object, period. If it does, a simple cast via (IDictionary<string, dynamic>)localVar will suffice.
If it's not, there are two things you can do:
Copy the IDictionary to your generic type.
Build an Adapter class that accepts the IDictionary as a dependency and implements the generic IDictionary you want, mapping calls from one to the other.
Edit: The sample code you've just posted will copy the dictionary every time it gets called! I will edit again in a moment with some suggested code.
Option 1
your sample code approach is solid as a means of copying the data, but the copy should be cached or you're going to copy lots of times. I'd suggest you put the actual translation code into a separate method and call that from your property the first time it's used. For example:
private IDictionary dataLayerProperties;
private IDictionary<string, dynamic> translatedProperties = null;
protected virtual IDictionary<string, dynamic> Properties
{
if(translatedProperties == null)
{
translatedProperties = TranslateDictionary(dataLayerProperties);
}
return translatedProperties;
}
public IDictionary<string, dynamic> TranslateDictionary(IDictionary values)
{
return values.Keys.Cast<string>().ToDictionary(key=>key, key => values[key] as dynamic);
}
Now, there are obvious cons to this approach... what if dataLayerProperties needs to be refreshed? You have to go setting translatedProperties to null again, etc.
Option 2
This is my preferred approach.
public class TranslatedDictionary : IDictionary<string, dynamic>
{
private IDictionary Original = null;
public TranslatedDictionary(IDictionary original)
{
Original = original;
}
public ICollection<string> Keys
{
get
{
return Original.Keys.Cast<string>().ToList();
}
}
public dynamic this[string key]
{
get
{
return Original[key] as dynamic;
}
set
{
Original[key] = value;
}
}
// and so forth, for each method of IDictionary<string, dynamic>
}
//elsewhere, using your original property and field names:
Properties = new TranslatedDictionary(properties);
Now, there are obvious cons to this approach as well, the most glaring is the fact that the Keys (and Value and anything else that returns ICollection on IDictionary has to return a new array for every call. But this still allows the most flexible approach, since it ensures the data is always up to date.
Unless the backing type for the IDictionary instance already implements IDictionary<string,dynamic> (like Dictionary<string,dynamic>) then casting won't help you. The Cast<>() method is only useful for returning IEnumerable<T> values and normal casting isn't an option.
If providing the data in the form of IDictionary<string,dynamic> is important, then why not go ahead and store it as a Dictionary<string,dynamic> from the start?
Here is a complete implementation of the approach recommended in the top answer. This was too big to fit as a comment.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
/// <summary>
/// The casted dictionary.
/// </summary>
/// <typeparam name="TKey">
/// The key type
/// </typeparam>
/// <typeparam name="TValue">
/// The value type
/// </typeparam>
public class CastedDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
/// <summary>
/// The original dictionary.
/// </summary>
private readonly IDictionary originalDictionary;
/// <summary>
/// The keys.
/// </summary>
private ICollection<TKey> keys;
/// <summary>
/// The values.
/// </summary>
private ICollection<TValue> values;
/// <summary>
/// Initializes a new instance of the <see cref="CastedDictionary{TKey,TValue}"/> class.
/// </summary>
/// <param name="original">
/// The original.
/// </param>
public CastedDictionary(IDictionary original)
: this()
{
if (original == null)
{
throw new ArgumentNullException("original");
}
this.originalDictionary = original;
}
/// <summary>
/// Prevents a default instance of the <see cref="CastedDictionary{TKey, TValue}"/> class from being created.
/// </summary>
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1642:ConstructorSummaryDocumentationMustBeginWithStandardText", Justification = "Style Cop does not analyze private generic class constructor comments properly")]
private CastedDictionary()
{
}
/// <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 this.originalDictionary.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 this.originalDictionary.IsReadOnly;
}
}
/// <summary>
/// Gets an <see cref="T:System.Collections.Generic.ICollection`1"/> containing the keys of the <see cref="T:System.Collections.Generic.IDictionary`2"/>.
/// </summary>
/// <returns>
/// An <see cref="T:System.Collections.Generic.ICollection`1"/> containing the keys of the object that implements <see cref="T:System.Collections.Generic.IDictionary`2"/>.
/// </returns>
public ICollection<TKey> Keys
{
get
{
return this.keys ?? (this.keys = this.originalDictionary.Keys.Cast<TKey>().ToList());
}
}
/// <summary>
/// Gets an <see cref="T:System.Collections.Generic.ICollection`1"/> containing the values in the <see cref="T:System.Collections.Generic.IDictionary`2"/>.
/// </summary>
/// <returns>
/// An <see cref="T:System.Collections.Generic.ICollection`1"/> containing the values in the object that implements <see cref="T:System.Collections.Generic.IDictionary`2"/>.
/// </returns>
public ICollection<TValue> Values
{
get
{
return this.values ?? (this.values = this.originalDictionary.Values.Cast<TValue>().ToList());
}
}
/// <summary>
/// Gets or sets the element with the specified key.
/// </summary>
/// <returns>
/// The element with the specified key.
/// </returns>
/// <param name="key">The key of the element to get or set.</param><exception cref="T:System.ArgumentNullException"><paramref name="key"/> is null.</exception><exception cref="T:System.Collections.Generic.KeyNotFoundException">The property is retrieved and <paramref name="key"/> is not found.</exception><exception cref="T:System.NotSupportedException">The property is set and the <see cref="T:System.Collections.Generic.IDictionary`2"/> is read-only.</exception>
public TValue this[TKey key]
{
get
{
return (TValue)this.originalDictionary[key];
}
set
{
this.originalDictionary[key] = value;
}
}
/// <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<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return this.originalDictionary.Cast<KeyValuePair<TKey, TValue>>().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 this.GetEnumerator();
}
/// <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(KeyValuePair<TKey, TValue> item)
{
this.originalDictionary.Add(item.Key, item.Value);
}
/// <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()
{
this.originalDictionary.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(KeyValuePair<TKey, TValue> item)
{
return this.originalDictionary.Contains(item.Key) && EqualityComparer<TValue>.Default.Equals(this[item.Key], item.Value);
}
/// <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">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"/>.</exception>
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
this.originalDictionary.CopyTo(array, arrayIndex);
}
/// <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(KeyValuePair<TKey, TValue> item)
{
if (this.Contains(item))
{
this.originalDictionary.Remove(item.Key);
return true;
}
return false;
}
/// <summary>
/// Determines whether the <see cref="T:System.Collections.Generic.IDictionary`2"/> contains an element with the specified key.
/// </summary>
/// <returns>
/// true if the <see cref="T:System.Collections.Generic.IDictionary`2"/> contains an element with the key; otherwise, false.
/// </returns>
/// <param name="key">The key to locate in the <see cref="T:System.Collections.Generic.IDictionary`2"/>.</param><exception cref="T:System.ArgumentNullException"><paramref name="key"/> is null.</exception>
public bool ContainsKey(TKey key)
{
return this.originalDictionary.Contains(key);
}
/// <summary>
/// Adds an element with the provided key and value to the <see cref="T:System.Collections.Generic.IDictionary`2"/>.
/// </summary>
/// <param name="key">The object to use as the key of the element to add.</param><param name="value">The object to use as the value of the element to add.</param><exception cref="T:System.ArgumentNullException"><paramref name="key"/> is null.</exception><exception cref="T:System.ArgumentException">An element with the same key already exists in the <see cref="T:System.Collections.Generic.IDictionary`2"/>.</exception><exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.IDictionary`2"/> is read-only.</exception>
public void Add(TKey key, TValue value)
{
this.originalDictionary.Add(key, value);
}
/// <summary>
/// Removes the element with the specified key from the <see cref="T:System.Collections.Generic.IDictionary`2"/>.
/// </summary>
/// <returns>
/// true if the element is successfully removed; otherwise, false. This method also returns false if <paramref name="key"/> was not found in the original <see cref="T:System.Collections.Generic.IDictionary`2"/>.
/// </returns>
/// <param name="key">The key of the element to remove.</param><exception cref="T:System.ArgumentNullException"><paramref name="key"/> is null.</exception><exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.IDictionary`2"/> is read-only.</exception>
public bool Remove(TKey key)
{
if (this.ContainsKey(key))
{
this.originalDictionary.Remove(key);
return true;
}
return false;
}
/// <summary>
/// Gets the value associated with the specified key.
/// </summary>
/// <returns>
/// true if the object that implements <see cref="T:System.Collections.Generic.IDictionary`2"/> contains an element with the specified key; otherwise, false.
/// </returns>
/// <param name="key">The key whose value to get.</param><param name="value">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 <paramref name="value"/> parameter. This parameter is passed uninitialized.</param><exception cref="T:System.ArgumentNullException"><paramref name="key"/> is null.</exception>
public bool TryGetValue(TKey key, out TValue value)
{
// ReSharper disable CompareNonConstrainedGenericWithNull
if (typeof(TKey).IsValueType == false && key == null)
// ReSharper restore CompareNonConstrainedGenericWithNull
{
throw new ArgumentNullException("key");
}
if (this.ContainsKey(key))
{
value = this[key];
return true;
}
value = default(TValue);
return false;
}
}
You need to use a KeyValuePair.
myDictionary.Cast<KeyValuePair<Type1, Type2>>()