How would you implement the IEnumerator interface? - c#

I have a class that map objects to objects, but unlike dictionary it maps them both ways. I am now trying to implement a custom IEnumerator interface that iterates through the values.
public class Mapper<K,T> : IEnumerable<T>, IEnumerator<T>
{
C5.TreeDictionary<K,T> KToTMap = new TreeDictionary<K,T>();
C5.HashDictionary<T,K> TToKMap = new HashDictionary<T,K>();
public void Add(K key, T value)
{
KToTMap.Add(key, value);
TToKMap.Add(value, key);
}
public int Count
{
get { return KToTMap.Count; }
}
public K this[T obj]
{
get
{
return TToKMap[obj];
}
}
public T this[K obj]
{
get
{
return KToTMap[obj];
}
}
public IEnumerator<T> GetEnumerator()
{
return KToTMap.Values.GetEnumerator();
}
public T Current
{
get { throw new NotImplementedException(); }
}
public void Dispose()
{
throw new NotImplementedException();
}
object System.Collections.IEnumerator.Current
{
get { throw new NotImplementedException(); }
}
public bool MoveNext()
{
;
}
public void Reset()
{
throw new NotImplementedException();
}
}

First, don't make your collection object implement IEnumerator<>. This leads to bugs. (Consider the situation where two threads are iterating over the same collection).
Implementing an enumerator correctly turns out to be non-trivial, so C# 2.0 added special language support for doing it, based on the 'yield return' statement.
Raymond Chen's recent series of blog posts ("The implementation of iterators in C# and its consequences") is a good place to get up to speed.
Part 1: https://web.archive.org/web/20081216071723/http://blogs.msdn.com/oldnewthing/archive/2008/08/12/8849519.aspx
Part 2: https://web.archive.org/web/20080907004812/http://blogs.msdn.com/oldnewthing/archive/2008/08/13/8854601.aspx
Part 3: https://web.archive.org/web/20080824210655/http://blogs.msdn.com/oldnewthing/archive/2008/08/14/8862242.aspx
Part 4: https://web.archive.org/web/20090207130506/http://blogs.msdn.com/oldnewthing/archive/2008/08/15/8868267.aspx

Just implement the IEnumerable<T> interface. No need to implement the IEnumerator<T> unless you want to do some special things in the enumerator, which for your case doesn't seem to be needed.
public class Mapper<K,T> : IEnumerable<T> {
public IEnumerator<T> GetEnumerator()
{
return KToTMap.Values.GetEnumerator();
}
}
and that's it.

CreateEnumerable() returns an IEnumerable which implements GetEnumerator()
public class EasyEnumerable : IEnumerable<int> {
IEnumerable<int> CreateEnumerable() {
yield return 123;
yield return 456;
for (int i = 0; i < 6; i++) {
yield return i;
}//for
}//method
public IEnumerator<int> GetEnumerator() {
return CreateEnumerable().GetEnumerator();
}//method
IEnumerator IEnumerable.GetEnumerator() {
return CreateEnumerable().GetEnumerator();
}//method
}//class

Use yield return.
What is the yield keyword used for in C#?

Here's an example from the book "Algorithms (4th Edition) by Robert Sedgewick".
It was written in java and i basically rewrote it in C#.
public class Stack<T> : IEnumerable<T>
{
private T[] array;
public Stack(int n)
{
array = new T[n];
}
public Stack()
{
array = new T[16];
}
public void Push(T item)
{
if (Count == array.Length)
{
Grow(array.Length * 2);
}
array[Count++] = item;
}
public T Pop()
{
if (Count == array.Length/4)
{
Shrink(array.Length/2);
}
return array[--Count];
}
private void Grow(int size)
{
var temp = array;
array = new T[size];
Array.Copy(temp, array, temp.Length);
}
private void Shrink(int size)
{
Array temp = array;
array = new T[size];
Array.Copy(temp,0,array,0,size);
}
public int Count { get; private set; }
public IEnumerator<T> GetEnumerator()
{
return new ReverseArrayIterator(Count,array);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
// IEnumerator implementation
private class ReverseArrayIterator : IEnumerator<T>
{
private int i;
private readonly T[] array;
public ReverseArrayIterator(int count,T[] array)
{
i = count;
this.array = array;
}
public void Dispose()
{
}
public bool MoveNext()
{
return i > 0;
}
public void Reset()
{
}
public T Current { get { return array[--i]; } }
object IEnumerator.Current
{
get { return Current; }
}
}
}

Related

C# HashSet<T> read-only workaround

Here is this sample code:
static class Store
{
private static List<String> strList = new List<string>();
private static HashSet<String> strHashSet = new HashSet<string>();
public static List<String> NormalList
{
get { return strList; }
}
public static HashSet<String> NormalHashSet
{
get { return strHashSet; }
}
public static IReadOnlyList<String> ReadonlyList
{
get { return (IReadOnlyList<String>)strList; }
}
public static IReadOnlyCollection<String> ReadonlyHashSet
{
get { return (IReadOnlyCollection<String>)strHashSet; }
}
public static IReadOnlyList<String> Real_ReadonlyList
{
get { return (IReadOnlyList<String>)strList.AsReadOnly(); }
}
public static IReadOnlyCollection<String> Real_ReadonlyHashSet
{
get
{
List<String> tmpList = new List<String>(strHashSet);
return (IReadOnlyList<String>)(tmpList).AsReadOnly();
}
}
}
And here is a test code:
// normal behaviour
// you can modify the list and the hashset
Store.NormalList.Add("some string 1");
Store.NormalHashSet.Add("some string 1");
// tricky behaviour
// you can still modify the list and the hashset
((List<String>)Store.ReadonlyList).Add("some string 2");
((HashSet<String>)Store.ReadonlyHashSet).Add("some string 2");
// expected read-only behaviour
// you can NOT modify
// throws InvalidCastException
((List<String>)Store.Real_ReadonlyList).Add("some string 3");
// throws InvalidCastException
((HashSet<String>)Store.Real_ReadonlyHashSet).Add("some string 3");
My questions are these:
Is there a better solution for the "Real_ReadonlyHashSet" property?
Will Microsoft some day implement the "AsReadOnly" method to the HashSet<T>?
Here is the entirety of the code of .AsReadOnly()
public ReadOnlyCollection<T> AsReadOnly() {
Contract.Ensures(Contract.Result<ReadOnlyCollection<T>>() != null);
return new ReadOnlyCollection<T>(this);
}
The first line is not even necessary if you are not using CodeContracts. However, ReadOnlyCollection<T> only supports IList<T> which HashSet<T> does not support.
What I would do is make your own ReadOnlySet<T> class that takes in a ISet<T> and only passes through the read operations like ReadOnlyCollection<T> does internally.
UPDATE:
Here is a fully fleshed out ReadOnlySet<T> I quickly wrote up along with a extension method that adds a .AsReadOnly() on to anything that implements ISet<T>
public static class SetExtensionMethods
{
public static ReadOnlySet<T> AsReadOnly<T>(this ISet<T> set)
{
return new ReadOnlySet<T>(set);
}
}
public class ReadOnlySet<T> : IReadOnlyCollection<T>, ISet<T>
{
private readonly ISet<T> _set;
public ReadOnlySet(ISet<T> set)
{
_set = set;
}
public IEnumerator<T> GetEnumerator()
{
return _set.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable) _set).GetEnumerator();
}
void ICollection<T>.Add(T item)
{
throw new NotSupportedException("Set is a read only set.");
}
public void UnionWith(IEnumerable<T> other)
{
throw new NotSupportedException("Set is a read only set.");
}
public void IntersectWith(IEnumerable<T> other)
{
throw new NotSupportedException("Set is a read only set.");
}
public void ExceptWith(IEnumerable<T> other)
{
throw new NotSupportedException("Set is a read only set.");
}
public void SymmetricExceptWith(IEnumerable<T> other)
{
throw new NotSupportedException("Set is a read only set.");
}
public bool IsSubsetOf(IEnumerable<T> other)
{
return _set.IsSubsetOf(other);
}
public bool IsSupersetOf(IEnumerable<T> other)
{
return _set.IsSupersetOf(other);
}
public bool IsProperSupersetOf(IEnumerable<T> other)
{
return _set.IsProperSupersetOf(other);
}
public bool IsProperSubsetOf(IEnumerable<T> other)
{
return _set.IsProperSubsetOf(other);
}
public bool Overlaps(IEnumerable<T> other)
{
return _set.Overlaps(other);
}
public bool SetEquals(IEnumerable<T> other)
{
return _set.SetEquals(other);
}
public bool Add(T item)
{
throw new NotSupportedException("Set is a read only set.");
}
public void Clear()
{
throw new NotSupportedException("Set is a read only set.");
}
public bool Contains(T item)
{
return _set.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
_set.CopyTo(array, arrayIndex);
}
public bool Remove(T item)
{
throw new NotSupportedException("Set is a read only set.");
}
public int Count
{
get { return _set.Count; }
}
public bool IsReadOnly
{
get { return true; }
}
}
Starting from .NET 5, the HashSet<T> class now implements the IReadOnlySet<T> interface. There is no built-in ReadOnlySet<T> wrapper though, analogous to the existing ReadOnlyDictionary<TKey, TValue> class for dictionaries, but implementing one is trivial:
public class ReadOnlySet<T> : IReadOnlySet<T>
{
private readonly ISet<T> _set;
public ReadOnlySet(ISet<T> set) { ArgumentNullException.ThrowIfNull(set); _set = set; }
public int Count => _set.Count;
public bool Contains(T item) => _set.Contains(item);
public bool IsProperSubsetOf(IEnumerable<T> other) => _set.IsProperSubsetOf(other);
public bool IsProperSupersetOf(IEnumerable<T> other) => _set.IsProperSupersetOf(other);
public bool IsSubsetOf(IEnumerable<T> other) => _set.IsSubsetOf(other);
public bool IsSupersetOf(IEnumerable<T> other) => _set.IsSupersetOf(other);
public bool Overlaps(IEnumerable<T> other) => _set.Overlaps(other);
public bool SetEquals(IEnumerable<T> other) => _set.SetEquals(other);
public IEnumerator<T> GetEnumerator() => _set.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
The upcoming .NET 7 will also feature a new AsReadOnly extension method for IDictionary<TKey,TValue>s, so let's make one for ISet<T>s too:
public static ReadOnlySet<T> AsReadOnly<T>(this ISet<T> set) => new ReadOnlySet<T>(set);
Usage example:
HashSet<Item> items = new();
ReadOnlySet<Item> readOnlyItems = items.AsReadOnly();
You could write your own implementation of an IReadOnlyCollection<T> that wraps an IEnumerable<T> and a count:
public sealed class ReadOnlyCollectionFromEnumerable<T>: IReadOnlyCollection<T>
{
readonly IEnumerable<T> _data;
public ReadOnlyCollectionFromEnumerable(IEnumerable<T> data, int count)
{
_data = data;
Count = count;
}
public IEnumerator<T> GetEnumerator()
{
return _data.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public int Count { get; }
}
Then you declare your ReadonlyHashSet property like this:
public static IReadOnlyCollection<String> ReadonlyHashSet
{
get { return new ReadOnlyCollectionFromEnumerable<string>(strHashSet, strHashSet.Count); }
}
I think that would solve the issue.
HashSet implements the IReadOnlyCollection interface starting
with the .NET Framework 4.6; in previous versions of the .NET
Framework, the HashSet class did not implement this interface.
Read in learn.microsoft.com
In .NET Framework 4.6 release, the HashSet implements IReadOnlyCollection interface along with the ISet interface. link...
It does seem to.
You could also do this but maybe take a performance hit:
var foo = (IReadOnlyCollection<string>) mySet.toList();

Implementation of IEnumerator without using 'yield return'

I'm learning iterator concepts in C# and was experimenting codes, taking simple issue and trying to implement in different ways. I'm trying to display all the terms in a list, for that I'm trying different ways to obtain the results. In the below code, I'm using two classes ListIterator and ImplementList.
In the ListIterator class : I defined a HashSet and it uses IEnumerator to store the values. Here GetEnumerator() method returns the values in the list. GetEnumerator is implemented in the ImplementList class (other class). Finally, the list is displayed in the console.
public class ListIterator
{
public void DisplayList()
{
HashSet<int> myhashSet = new HashSet<int> { 30, 4, 27, 35, 96, 34};
IEnumerator<int> IE = myhashSet.GetEnumerator();
while (IE.MoveNext())
{
int x = IE.Current;
Console.Write("{0} ", x);
}
Console.WriteLine();
Console.ReadKey();
}
}
In the ImplementList class : GetEnumerator() is defined and it returns the list using yield return x.
public class ImplementList : IList<int>
{
private List<int> Mylist = new List<int>();
public ImplementList() { }
public void Add(int item)
{
Mylist.Add(item);
}
public IEnumerator<int> GetEnumerator()
{
foreach (int x in Mylist)
yield return x;
}
}
Now, I want to rewrite the GetEnumerator() without using yield return. And it should return all the values in a list. Is it possible to get all the values in the list without using yield return in IEnumerator
You can use the Enumerator implementation of you inner list MyList:
public IEnumerator<int> GetEnumerator()
{
return MyList.GetEnumerator();
}
Or you can implement an IEnumerator yourself (from MSDN):
public class People : IEnumerable
{
private Person[] _people;
public People(Person[] pArray)
{
_people = new Person[pArray.Length];
for (int i = 0; i < pArray.Length; i++)
{
_people[i] = pArray[i];
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return (IEnumerator) GetEnumerator();
}
public PeopleEnum GetEnumerator()
{
return new PeopleEnum(_people);
}
}
public class PeopleEnum : IEnumerator
{
public Person[] _people;
// Enumerators are positioned before the first element
// until the first MoveNext() call.
int position = -1;
public PeopleEnum(Person[] list)
{
_people = list;
}
public bool MoveNext()
{
position++;
return (position < _people.Length);
}
public void Reset()
{
position = -1;
}
object IEnumerator.Current
{
get
{
return Current;
}
}
public Person Current
{
get
{
try
{
return _people[position];
}
catch (IndexOutOfRangeException)
{
throw new InvalidOperationException();
}
}
}
}
This would return results as an array
return MyList.ToArray();
or if you want to return it as a List, why not just
return MyList;

Is There a C# Generic Container That Is List<T> but also HashSet<T>

I'm looking for a C# generic container that it's a List<T>, but no repeated elements allowed.
In another word, it's a Set<T>, but can also be accessed via [index] operator.
Thanks.
As suggested in the comments, you could implement IList<T> that delegates to an inner List<T> instance, and guards add and remove calls with using a HashSet<T>:
public class UniqueList<T> : IList<T>
{
private readonly List<T> list=new List<T>();
private readonly HashSet<T> set=new HashSet<T>();
public IEnumerator<T> GetEnumerator()
{
return list.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Add(T item)
{
if(set.Add(item))
{
list.Add(item);
}
}
public void Clear()
{
set.Clear();
list.Clear();
}
public bool Contains(T item)
{
return set.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
list.CopyTo(array,arrayIndex);
}
public bool Remove(T item)
{
if(set.Remove(item))
{
list.Remove(item);
return true;
}
return false;
}
public int Count { get { return list.Count; } }
public bool IsReadOnly { get { return false; } }
public int IndexOf(T item)
{
return list.IndexOf(item);
}
public void Insert(int index, T item)
{
if(set.Add(item))
{
list.Insert(index, item);
}
}
public void RemoveAt(int index)
{
T item = list[index];
set.Remove(item);
list.RemoveAt(index);
}
public T this[int index]
{
get { return list[index]; }
set {
T item = list[index];
set.Remove(item);
if(set.Add(value))
{
list[index] = value;
}
else
{
set.Add(item);
throw new Exception();
}
}
}
}
I didn't compile this code, but you get the idea...
No such thing is provided in the framework (and HashSet<T> does not guarantee any specific order, so you can't cheat with ElementAt). The closest inbuilt you could use would be something like SortedList<T,anything> (the "anything" there doesn't matter, and could be T, int, whatever), for example:
var data = new SortedList<string, int>();
data["abc"] = 1;
data["def"] = 1;
data["abc"] = 1;
var thisIsTrue = data.ContainsKey("def");
var thisIsFalse = data.ContainsKey("ghi");
for (int i = 0; i < data.Count; i++) // 2 iterations
Console.WriteLine(data.Keys[i]); // abc, def
However; it is important to note that the guaranteed order here is key order, not insertion order. The index of keys is available via data.IndexOfKey.
You could use the OrderedDictionary. If you use your type for both key and value type, and use the same object as both key and value, you get the behavior you need (You could also use the keys collection for index based retrieval; and stick dummy data into the value, but I'm not sure what you would gain).

C# - Can a List<MyClass> be seamlessly cast to a List<Interface> or similar?

I have a DataSource in my control which is always a List<T> where T has to inherit from IEntity.
public class MyClass<T> where T : IEntity
{
public List<T> DataSource
{
get;
set;
}
}
Now, obviously you can't cast a List<T> to a List<IEntity> doing the following:
List<IEntity> wontWork = (List<IEntity>)this.DataSource;
How can I get the DataSource as a List of IEntity, whilst still being able to add and remove items from the DataSource? I.e. I could do the following, but removing from the List it returns would not remove from the DataSource:
public List<TOut> GetDataSourceCopyAsUnderlyingType<TOut>()
{
if (this.DataSource == null)
{
return new List<TOut>();
}
else
{
// Get the list and the enumerator
IList list = (IList)this.DataSource;
IEnumerator enumerator = list.GetEnumerator();
// Build the target list
List<TOut> targetList = new List<TOut>();
int i = 0;
while (enumerator.MoveNext())
{
TOut entity = (TOut)list[i];
targetList.Add(entity);
i++;
}
return targetList;
}
}
Basically, I need some way of doing the following:
List<IEntity> interfaceList = this.GetDataSourceAsAnotherType<IEntity>();
int dataSourceCount = this.DataSource.Count; // Equals 5
int interfaceCount = interfaceList.Count; // Equals 5
interfaceList.RemoveAt(0);
int dataSourceCount = this.DataSource.Count; // Equals 4
int interfaceCount = interfaceList.Count; // Equals 4
And just to add, I don't mind if it means I've got to use a different type instead of a List.
EDIT: Sorry, forgot to say I'm using .Net2.0 and cannot move to .Net 3.5.
It would be a monumentally bad idea if this were allowed, which is why it isn't. I can add any old IEntity to a List<IEntity> which will blow up if that IEntity can't be cast to T. Whilst all Ts are IEntities, not all IEntities are Ts.
This works with arrays because arrays have a deliberate subtyping hole (as they do in Java). Collections do not have a subtyping hole.
Create a wrapper class that seamlessly converts. Untested sample:
public class CastList<TTarget, TOriginal>
: IList<TTarget> where TOriginal : TTarget
{
List<TOriginal> _orig;
public CastList(List<TOriginal> orig) { _orig = orig; }
public Add(TTarget item) { _orig.Add(item); }
public TTarget this[int i]
{
get { return (TTarget)_orig[i]; }
set { _orig[i] = value; }
}
public IEnumerator<TTarget> GetEnumerator()
{
foreach(TOriginal item in _orig)
yield return (TTarget)item;
}
// etc...
}
Manipulations of the original list will also be reflected in the wrapper. To use this, just construct it with your DataSource.
What DrPizza said, but with more code:
public class ListFacade<TIn, TOut> : IList<TOut> where TIn : TOut
{
private readonly IList<TIn> innerList;
public ListFacade(IList<TIn> innerList)
{
this.innerList = innerList;
}
public int Count
{
get { return this.innerList.Count; }
}
public bool IsReadOnly
{
get { return this.innerList.IsReadOnly; }
}
public TOut this[int index]
{
get { return this.innerList[index]; }
set { this.innerList[index] = (TIn)value; }
}
public void Add(TOut item)
{
this.innerList.Add((TIn)item);
}
public void Clear()
{
this.innerList.Clear();
}
public bool Contains(TOut item)
{
return (item is TIn) && this.innerList.Contains((TIn)item);
}
public void CopyTo(TOut[] array, int arrayIndex)
{
var inArray = new TIn[this.innerList.Count];
this.innerList.CopyTo(inArray, arrayIndex);
Array.Copy(inArray, array, inArray.Length);
}
public IEnumerator<TOut> GetEnumerator()
{
foreach (var item in this.innerList)
{
yield return item;
}
}
System.Collections.IEnumerator
System.Collections.IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
public int IndexOf(TOut item)
{
return (item is TIn) ? this.innerList.IndexOf((TIn)item) : -1;
}
public void Insert(int index, TOut item)
{
this.innerList.Insert(index, (TIn)item);
}
public bool Remove(TOut item)
{
return (item is TIn) && this.innerList.Remove((TIn)item);
}
public void RemoveAt(int index)
{
this.innerList.RemoveAt(index);
}
Add, Insert and the indexer set will blow up if the argument is not of type TIn.
ok this might be completely beside the point but, how about using a little bit of Linq?
var interfaceList = objectList.ConvertAll<Interface>(o => (Interface)o);
this way you can cast the objectList easily.
hope this helps to find the solution...
I'm in favor of linq too, but you can do it like:
var interfaceList = objectList.Cast<IEntity>();
Which is shorter and more expressive.

In C# 4.0 why can't an out parameter in a method be covariant?

Given this magical interface:
public interface IHat<out TRabbit>
{
TRabbit Take();
}
And this class hierarchy:
public class Rabbit { }
public class WhiteRabbit : Rabbit { }
I can now compile this:
IHat<WhiteRabbit> hat1 = null;
IHat<Rabbit> hat2 = hat1;
Which is great. But what if I define the interface differently:
public interface IHat<out TRabbit>
{
bool Take(out TRabbit r);
}
I'm indicating that the hat might be empty, using a separate boolean return value (the previous version would perhaps have returned a null rabbit from an empty hat). But I'm still only outputting a rabbit, so not doing anything logically different to the previous version.
The C# 4.0 compiler in the CTP gives an error in the interface definition - it requires 'out' method parameters to be of an invariant type. Is there a hard-and-fast reason why this isn't allowed, or is it something that might be addressed in a future version?
Interesting. However, at the CLI level there is no such thing as "out" - only "ref"; there is an attribute that helps compilers (for definite assignment) that says "you don't need to pass it in".
Maybe this restriction is because the CLI doesn't have "out", only "ref".
Although it's a bit of a hassle, you can use a covariance wrapper:
public class CovariantListWrapper<TOut, TIn> : IList<TOut> where TIn : TOut
{
IList<TIn> list;
public CovariantListWrapper(IList<TIn> list)
{
this.list = list;
}
public int IndexOf(TOut item)
{
// (not covariant but permitted)
return item is TIn ? list.IndexOf((TIn)item) : -1;
}
public TOut this[int index]
{
get { return list[index]; }
set { throw new InvalidOperationException(); }
}
public bool Contains(TOut item)
{
// (not covariant but permitted)
return item is TIn && list.Contains((TIn)item);
}
public void CopyTo(TOut[] array, int arrayIndex)
{
foreach (TOut t in this)
array[arrayIndex++] = t;
}
public int Count { get { return list.Count; } }
public bool IsReadOnly { get { return true; } }
public IEnumerator<TOut> GetEnumerator()
{
foreach (TIn t in list)
yield return t;
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Insert(int index, TOut item) { throw new InvalidOperationException(); }
public void RemoveAt(int index) { throw new InvalidOperationException(); }
public void Add(TOut item) { throw new InvalidOperationException(); }
public void Clear() { throw new InvalidOperationException(); }
public bool Remove(TOut item) { throw new InvalidOperationException(); }
}
This lets you keep the collection as it was originally typed and refer to it covariantly without creating a detached copy, so that updates to the original are seen in the covariant use. Example:
class CovarianceWrapperExample
{
class Person { }
class Employee : Person { }
void ProcessPeople(IList<Person> people) { /* ... */ }
void Foo()
{
List<Employee> employees = new List<Employee>();
// cannot do:
ProcessPeople(employees);
// can do:
ProcessPeople(new CovariantListWrapper<Person, Employee>(employees));
}
}

Categories

Resources