Summary#
An array literal [a, b, c] is a value expression — a list you can return, assign to a local, pass as an
argument, or hand to a UI control as a prop. The element type is inferred from the items (a homogeneous list is the
common case). Because object literals (new T { … }) are plain data, a list of them is a first-class value too — so
a UI prop like columns: [ new GridColumn<User> { Name = "Email", Value = u => u.Email } ] is expressed directly,
no workaround.
Signature#
[1, 2, 3] // int[]
["Email", "DisplayName"] // string[]
[ new GridColumn<User> { Name = "Email", Value = u => u.Email } ] // GridColumn<User>[]
[] // an empty listDescription#
- A value, anywhere a value fits — return it, assign it (
var xs = [1, 2, 3];), pass it as an argument, or use it as a render/state value in a component. - Element type is inferred from the items; a list of
new GridColumn<User> { … }is aGridColumn<User>[], matching a prop declaredGridColumn<T>[]. - Object literals inside are pure data. In a render position a
new T { … }builds a plain data object (it is not a stored row — that is what anew T { … }inside an action body does). This is why a control'scolumnsprop takes[ new GridColumn<T> { … } ]cleanly. - Distinct from set-membership. Testing membership is written
[a, b].Contains(x)(orx in [a, b]), which the compiler lowers to a set test — separate from using[a, b]as a value.
It takes the type it is written into#
As in C# 12, a literal is target-typed: written where a List<T>, a HashSet<T> or a byte[] is expected, it
is one — List<string> tags = []; needs no .ToList(). An empty [] written where a Dictionary<K, V> is
expected is an empty dictionary, and where a HashSet<T> is expected an empty set (a value added to it twice is held
once). The declaration decides, wherever it is written — a local, a class's field, a component's field — and the
value is the same collection on the server and in the browser. That holds for a return, an argument, a field initializer,
each branch of a ?:, and the right-hand side of ??, which takes the LEFT operand's collection type (or, failing
that, the type the whole expression is written into). So the null fallback C# code writes everywhere is just that:
class Draft { public List<string>? Tags; }
List<string> TagsOf(Draft d) => d.Tags ?? []; // the empty list, as a List<string>
List<string> TagsOrDefault(Draft d) { return d.Tags ?? ["untagged"]; }
int WithOneMore(Draft d) {
var tags = d.Tags ?? []; // a List<string> (the left side's type), so it can grow
tags.Add("new");
return tags.Count;
}[Test]
void The_fallback_takes_the_left_sides_type() {
Assert.Equal(0, TagsOf(new Draft()).Count);
Assert.Equal(["untagged"], TagsOrDefault(new Draft()));
Assert.Equal(["a"], TagsOrDefault(new Draft { Tags = ["a"] }));
Assert.Equal(1, WithOneMore(new Draft()));
}⚠ Only a literal is retyped. An existing T[] on the right of ?? is not turned into a List<T>: an array is a
read-only sequence (usually a query result), and handing it back as a list would promise an .Add it cannot keep.
Write .ToList() when that is what you mean.
Where can new() leave out the type?#
C# 9's target-typed new() takes the type of the place it is written — a declared local, a class's or a component's
field (public Dictionary<string, string> Values = new();), a return, an argument, an initializer member: List<int> xs = new();, Point p = new();. With braces it is that type's initializer:
new() { 1, 2 } for a list or set, new() { ["k"] = 1 } for a dictionary, new() { X = 1 } for a class. Where
the position gives no type — a lambda's body, an interpolation hole, the right of ?? — C# casts, and so does Osy#:
(List<int>)[1, 2], (int[])[], (Point)new().
int Counts() {
List<int> xs = new(); xs.Add(1);
HashSet<int> ids = new() { 1, 2, 2 }; // a set: 2 elements
Dictionary<string, int> stock = new() { ["a"] = 3 };
return xs.Count + ids.Count + stock["a"] + ((List<int>)[7, 8]).Count; // 1 + 2 + 3 + 2
}Can a page send one to the server?#
Yes. A List<T>, a HashSet<T> and a Dictionary<K, V> are passed to a server function from a component's action
as any value is — as the argument itself, or as a member of a class that is — and come back the same way. A
dictionary or set made with StringComparer.OrdinalIgnoreCase still compares that way on the other side.
public class Reply {
public string Comment = "";
public Dictionary<string, string> Values = [];
}
public string Send(Reply reply) { return reply.Comment + ": " + reply.Values.Count; }
component Form() {
Dictionary<string, string> typed = [];
string said = "";
action Submit() { said = Send(new Reply { Comment = "done", Values = typed }); }
render { Pressable(onClick: Submit) { Text("Send " + said); } }
}Is there a Queue<T> or a Stack<T>?#
Not yet. The collections are List<T>, HashSet<T>, Dictionary<K, V> and arrays (T[]); new Queue<int>(),
new Stack<int>() and new LinkedList<int>() are refused by name, each with its stand-in. A list does all three
jobs:
/// First in, first out: 1 then 2.
int ServeNext() {
var queue = new List<int>();
queue.Add(1); queue.Add(2); // enqueue
var next = queue[0]; queue.RemoveAt(0); // dequeue
return next * 10 + queue[0]; // 12 — and queue[0] peeks
}
/// Last in, first out: 4 then 3.
int UndoLast() {
var stack = new List<int>();
stack.Add(3); stack.Add(4); // push
var top = stack[stack.Count - 1]; stack.RemoveAt(stack.Count - 1); // pop
return top * 10 + stack[stack.Count - 1]; // 43
}Examples#
int[] Small() { return [1, 2, 3]; }
string[] Names() { var xs = ["Ada", "Grace"]; return xs; }Supplying a control's typed columns from an array of object literals (a UI prop):
entity User {
[Required, MaxLength(200)] string Email;
[Required, MaxLength(120)] string DisplayName;
security { allow read when IsAuthenticated; }
}
[Route("/users")] [Render(CSR)]
component UsersPage() {
var users = User.ToList();
render {
DataGrid(
label: "Users",
rows: users,
columns: [
new GridColumn<User> { Name = "Email", Label = "Email", Value = u => u.Email },
new GridColumn<User> { Name = "DisplayName", Label = "Name", Value = u => u.DisplayName }
]
);
}
}See also#
- List indexer — reading an element by index (
xs[0]) - List OrderBy (in-memory) — sorting a collection
- control — foreign UI controls (charts, grids, maps) — foreign controls, whose typed props often take an array literal