I'm trying to learn about Interfaces in C# and came upon this code from Microsoft's github examples:
SampleCustomer c = new SampleCustomer("customer one", new DateTime(2010, 5, 31))
{
Reminders =
{
{ new DateTime(2010, 08, 12), "childs's birthday" },
{ new DateTime(1012, 11, 15), "anniversary" }
}
};
I understand that SampleCustomer is a constructor, but how is it that Reminders is included in the Constructor for SampleCustomer?
SampleCustomer looks like this:
public class SampleCustomer : ICustomer
{
public SampleCustomer(string name, DateTime dateJoined) =>
(Name, DateJoined) = (name, dateJoined);
private Dictionary<DateTime, string> _reminders = new Dictionary<DateTime, string>();
public IDictionary<DateTime, string> Reminders => _reminders;
}
And ICustomer's Reminders is defined in ICustomer as
IDictionary<DateTime, string> Reminders { get; }
And implemented in SampleCustomer
In that piece or code thare are 2 different concepts/language features.
One is "Object initializers":
https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/object-and-collection-initializers#object-initializers
Object initializers let you assign values to any accessible fields or properties of an object at creation time without having to invoke a constructor followed by lines of assignment statements.
the other is "Collection intializers":
https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/object-and-collection-initializers#collection-initializers
var moreNumbers = new Dictionary<int, string>
{
{19, "nineteen" },
{23, "twenty-three" },
{42, "forty-two" }
};
But in this case you are not instantiating a new dictionary but adding elements to it
You will not be able to use collection initializer syntax discussed so far since the property cannot be assigned a new list
However, new entries can be added to Cats (Reminders in your case) nonetheless using the initialization syntax by omitting the list creation
THats an acceptable initialization syntax. (an unusual one though, because it uses a constructor with arguments and public property initialization)
see here
https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/object-and-collection-initializers
Related
In C# you can initialise arrays and other objects, such as Lists, or even Dictionaries, like this:
Type[] array = new Type[] { new Type(), new Type(), new Type()...};
or, more bizarre looking, for Dictionaries:
Dictionary<string, object> dictionary = new Dictionary<string, object> () {
{ "key1", value },
{ "key2", value }
};
How would I write a constructor for my own type of object? In my case, I want to use it for an object I named LayerGroup:
LayerGroup layers = new LayerGroup() {
"background",
"foreground",
"menu",
"entities"
};
And I'd like to know how to do it in Dictionary style too:
LayerGroup layers = new LayerGroup() {
{ 4, "background" },
{ 1, "foreground" },
{ 0, "menu" },
{ 3, "entities" }
};
(In that case, I would of course have a different structure for the LayerGroup class, that's just an example)
To get collection initializer you'll need to implement 2 methods and inherit your class from IEnumerable:
public class LayerGroup: IEnumerable
{
private readonly IList<string> _collection = new List<string>();
public void Add(string value)
{
_collection.Add(value);
}
public IEnumerator GetEnumerator()
{
return _collection.GetEnumerator();
}
}
Similarly, if you want to get "Dictionary like" initialization, you need to implement
public void Add(string key, string value)
{
...
}
or whole IDictionary interface, if you want to get full spectrum of Dictionary functionality.
The array-style initializers are called Collection Initializers. In order for collection initializers to work, the object being initialized needs to implement IEnumerable, and provide the corresponding Add method, with the signature matching the parameters that you plan to supply.
Hence the LayerGroup class needs to contain an Add method that looks like this:
public void Add(string name);
This would let you initialize LayerGroup with a list-like initializer. If you provide an Add method like this
public void Add(int number, string name);
you would be able to use a dictionary-like initializer (i.e. the last snippet from your question).
Through some random object creation today I came across this neat little shortcut for a Dictionary<K, V>. Is the following assignment a compiler shortcut or is it a feature of Dictionary<string, string>.
IDictionary<string, string> items = { { "item1key", "item1value" } };
Looking at the source for Dictionary<K, V> I don't see anything offhand for how this works. Implementing all the interfaces for this class dot not allow me to perform a similar operation. Why is it that we can do it for a dictionary but not another type. For example, how does the compiler or language feature know that the first item is a key and the second item is the value. Or even more specific this same syntax can't be used for a List<string>
List<string> items = { "item1" };
So the first is valid, why?
I'm not necessarily trying to duplicate this but rather curious as to why it is the way it is. What makes a dictionary special in this case?
Example that works
public class Button
{
public string Title { get; set; }
public ButtonType Type { get; set; }
public IDictionary<string, string> Items { get; set; }
public bool RequiresSelected { get; set; }
}
var buttons = new List<Button>
{
new Button {
Items = {
{"button1", "Button 1"},
{"button2", "Button 2"},
{"button3", "Button 3"},
},
Title = "3 Buttons",
Type = ButtonType.DropDown
}
};
The syntax you've shown isn't valid in C#. You'd need:
IDictionary<string, string> items = new Dictionary<string, string>
{ { "item1key", "item1value" } };
At that point it's just a normal collection initializer, so the list equivalent would be:
List<string> items = new List<string> { "item1" };
EDIT: Let's see if my edit can beat yours. My guess is that you've seen something like:
var foo = new Foo {
SomeDictionaryProperty = {
{ "item1key", "item1value" }
}
};
That's an embedded collection initializer, and can be used for lists too. It's not creating a new dictionary, it's adding to an existing one. The code above is equivalent to:
var tmp = new Foo();
tmp.SomeDictionaryProperty.Add("item1key", "item1value");
var foo = tmp;
Another example of it working:
var form = new Form {
Controls = { new Label { Text = "Foo"}, new Label { Text = "Bar" } }
};
See section 7.6.10.2 of the C# 4 specification (Object Initializers) for more information. The important bit is this:
member-initializer:
identifier = initializer-value
initializer-value:
expression
object-or-collection-initializer
So you can initialize a property to either be a specific value (in which case the setter will be used) or via an object/collection initializer, in which case the getter for the property will be used, and then setters or the Add method will be used for the body of the object/collection initializer.
This is a feature of C# compiler, and the dictionary is not special: any collection that supports Add can be initialized in this way. The details are in the section 7.6.10.3 of the C# Language Specification 4.0.
The collection object to which a collection initializer is applied must be of a type that implements System.Collections.IEnumerable or a compile-time error occurs. For each specified element in order, the collection initializer invokes an Add method on the target object with the expression list of the element initializer as argument list, applying normal overload resolution for each invocation. Thus, the collection object must contain an applicable Add method for each element initializer.
Today I was surprised to find that in C# I can do:
List<int> a = new List<int> { 1, 2, 3 };
Why can I do this? What constructor is called? How can I do this with my own classes? I know that this is the way to initialize arrays but arrays are language items and Lists are simple objects ...
This is part of the collection initializer syntax in .NET. You can use this syntax on any collection you create as long as:
It implements IEnumerable (preferably IEnumerable<T>)
It has a method named Add(...)
What happens is the default constructor is called, and then Add(...) is called for each member of the initializer.
Thus, these two blocks are roughly identical:
List<int> a = new List<int> { 1, 2, 3 };
And
List<int> temp = new List<int>();
temp.Add(1);
temp.Add(2);
temp.Add(3);
List<int> a = temp;
You can call an alternate constructor if you want, for example to prevent over-sizing the List<T> during growing, etc:
// Notice, calls the List constructor that takes an int arg
// for initial capacity, then Add()'s three items.
List<int> a = new List<int>(3) { 1, 2, 3, }
Note that the Add() method need not take a single item, for example the Add() method for Dictionary<TKey, TValue> takes two items:
var grades = new Dictionary<string, int>
{
{ "Suzy", 100 },
{ "David", 98 },
{ "Karen", 73 }
};
Is roughly identical to:
var temp = new Dictionary<string, int>();
temp.Add("Suzy", 100);
temp.Add("David", 98);
temp.Add("Karen", 73);
var grades = temp;
So, to add this to your own class, all you need do, as mentioned, is implement IEnumerable (again, preferably IEnumerable<T>) and create one or more Add() methods:
public class SomeCollection<T> : IEnumerable<T>
{
// implement Add() methods appropriate for your collection
public void Add(T item)
{
// your add logic
}
// implement your enumerators for IEnumerable<T> (and IEnumerable)
public IEnumerator<T> GetEnumerator()
{
// your implementation
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
Then you can use it just like the BCL collections do:
public class MyProgram
{
private SomeCollection<int> _myCollection = new SomeCollection<int> { 13, 5, 7 };
// ...
}
(For more information, see the MSDN)
It is so called syntactic sugar.
List<T> is the "simple" class, but compiler gives a special treatment to it in order to make your life easier.
This one is so called collection initializer. You need to implement IEnumerable<T> and Add method.
According to the C# Version 3.0 Specification "The collection object to which a collection initializer is applied must be of a type that implements System.Collections.Generic.ICollection for exactly one T."
However, this information appears to be inaccurate as of this writing; see Eric Lippert's clarification in the comments below.
It works thanks to collection initializers which basically require the collection to implement an Add method and that will do the work for you.
Another cool thing about collection initializers is that you can have multiple overloads of Add method and you can call them all in the same initializer! For example this works:
public class MyCollection<T> : IEnumerable<T>
{
public void Add(T item, int number)
{
}
public void Add(T item, string text)
{
}
public bool Add(T item) //return type could be anything
{
}
}
var myCollection = new MyCollection<bool>
{
true,
{ false, 0 },
{ true, "" },
false
};
It calls the correct overloads. Also, it looks for just the method with name Add, the return type could be anything.
The array like syntax is being turned in a series of Add() calls.
To see this in a much more interesting example, consider the following code in which I do two interesting things that sound first illegal in C#, 1) setting a readonly property, 2) setting a list with a array like initializer.
public class MyClass
{
public MyClass()
{
_list = new List<string>();
}
private IList<string> _list;
public IList<string> MyList
{
get
{
return _list;
}
}
}
//In some other method
var sample = new MyClass
{
MyList = {"a", "b"}
};
This code will work perfectly, although 1) MyList is readonly and 2) I set a list with array initializer.
The reason why this works, is because in code that is part of an object intializer the compiler always turns any {} like syntax to a series of Add() calls which are perfectly legal even on a readonly field.
In Java, I can create an List and immediately populate it using a static initializer. Something like this:
List <String> list = new ArrayList<String>()
{{
Add("a");
Add("b");
Add("c");
}}
Which is convenient, because I can create the list on the fly, and pass it as an argument into a function. Something like this:
printList(new ArrayList<String>()
{{
Add("a");
Add("b");
Add("c");
}});
I am new to C# and trying to figure out how to do this, but am coming up empty. Is this possible in C#? And if so, how can it be done?
You can use a collection initializer:
new List<string> { "a", "b", "c" }
This compiles to a sequence of calls to the Add method.
If the Add method takes multiple arguments (eg, a dictionary), you'll need to wrap each call in a separate pair of braces:
new Dictionary<string, Exception> {
{ "a", new InvalidProgramException() },
{ "b", null },
{ "c", new BadImageFormatException() }
}
Since C# 3.0 you can do it as well:
List <String> list = new List<String>
{
"a", "b", "c"
};
MSDN, Collection Initializers
Collection initializers let you specify one or more element
intializers when you initialize a collection class that implements
IEnumerable. The element initializers can be a simple value, an
expression or an object initializer. By using a collection initializer
you do not have to specify multiple calls to the Add method of the
class in your source code; the compiler adds the calls.
EDIT: Answer to comment regarding dictionary
IDictionary<string, string> map = new Dictionary<string, string>
{
{ "Key0", "Value0" },
{ "Key1", "Value1" }
};
Yes, it's described on MSDN here
Is there any way to avoid the repetition of the type in this kind of declaration of a class member?
Dictionary<string, int> myDict = new Dictionary<string, int>();
No, you can only use var for local variables. Basically you're stuck with the repetition, I'm afraid.
Eric Lippert has a great blog post on this.
Interesting point to note: Java performs implicit typing and type inference the other way round, based on what you're trying to assign to. That means this is legal:
// Note: This is Java, not C#!
class CollectionHelpers
{
public static <T> List<T> newList()
{
return new ArrayList<T>();
}
}
// In another class (doesn't have to be static)
static List<String> names = CollectionHelpers.newList();
Sure - use VB.NET. ;)
myDict as New Dictionary(Of String, Integer)()
Though it's not directly relevant to the question, some people may be interested that you can do this in C#3 using collection initialisation:
var myDict = new Dictionary<string, int>()
{
{ "one", 1 },
{ "two", 2 },
{ "three", 3}
};