Skip to content

Commit a1785e4

Browse files
committed
Added destructuring and spread
1 parent c63a928 commit a1785e4

File tree

5 files changed

+328
-3
lines changed

5 files changed

+328
-3
lines changed

docs/.vitepress/config.mts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ export default defineConfig({
8181
{ text: "Comments", link: "/pages/basics/comments" },
8282
{ text: "Functions", link: "/pages/basics/functions" },
8383
{ text: "Classes and Types", link: "/pages/basics/classes" },
84+
{
85+
text: "Destructuring and Spread",
86+
link: "/pages/basics/destructuring-spread",
87+
},
8488
{ text: "Generics", link: "/pages/basics/generics" },
8589
{ text: "Async/Await", link: "/pages/basics/async-await" },
8690
{ text: "Packages vs Projects", link: "/pages/basics/projects" },

docs/pages/advanced/reflection.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# Reflection
22

3-
C# offers robust **reflection** capabilities through the `System.Reflection` namespace, allowing developers to inspect metadata about types at runtime, such as properties, methods, fields, and their attributes. Reflection in C# is strongly integrated into the language and type system, enabling operations like dynamic method invocation, creating instances of types, and accessing private members, all while maintaining type safety. This allows for powerful scenarios such as dependency injection, serialization, and building flexible, reusable frameworks. Reflection in C# also works seamlessly with generics and provides detailed access to the structure of objects and assemblies, making it a critical tool in advanced scenarios.
3+
C# uses **reflection** capabilities through the `System.Reflection` namespace, allowing developers to inspect metadata about types at runtime such as properties, methods, fields, and their attributes. Reflection in C# is strongly integrated into the language and type system, enabling operations like dynamic method invocation, creating instances of types, and accessing private members, all while maintaining type safety. This allows for powerful scenarios such as dependency injection, serialization, and building flexible, reusable frameworks. Reflection in C# also works seamlessly with generics and provides detailed access to the structure of objects and assemblies, making it a critical tool in advanced scenarios.
44

5-
In contrast, **TypeScript/JavaScript** provides some limited reflection capabilities, but they are not as deeply integrated into the language. JavaScript can inspect objects using features like `typeof`, `instanceof`, or `Object.getOwnPropertyNames()`, but it lacks the comprehensive type introspection found in C#. TypeScript adds some support for type reflection during development through type annotations and the `typeof` operator, but this is mainly for compile-time type checking, not runtime introspection. To achieve more advanced reflection-like functionality, such as schema validation or dynamically interacting with object properties, developers often rely on third-party libraries (like `class-transformer`, `class-validator`, or `reflect-metadata`). Additionally, TypeScript’s type system doesn’t exist at runtime, so true runtime type introspection requires extra effort, including manual schema definitions or metadata decorators, making it less seamless than C#'s built-in reflection tools.
5+
In contrast, **TypeScript/JavaScript** has traditionally provided some limited reflection capabilities. JavaScript can inspect objects using features like `typeof`, `instanceof`, or `Object.getOwnPropertyNames()`, but it lacks the comprehensive type introspection found in C#. TypeScript adds some support for type reflection during development through type annotations and the `typeof` operator, but this is mainly for compile-time type checking, not runtime introspection. To achieve more advanced reflection-like functionality, such as schema validation or dynamically interacting with object properties, developers often rely on third-party libraries (like `class-transformer`, `class-validator`, or `reflect-metadata`). Additionally, TypeScript’s type system doesn’t exist at runtime, so true runtime type introspection requires extra effort, including manual schema definitions (e.g. Zod) or metadata decorators, making it less seamless than C#'s built-in reflection tools.
66

77
## Basics
88

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
# Destructuring and Spread Operations
2+
3+
C# supports [a limited set of destructuring](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/functional/deconstruct) and spread operations compared to JS and while generally useful, are not as powerful (and potentially dangerous?) as JS destructuring and spread.
4+
5+
In JavaScript, destructuring and spread a commonly used to perform object shape manipulations and transformations while in C#, they are more restricted in that sense and are more basic operations.
6+
7+
Let's take a look.
8+
9+
## Destructuring Assignment
10+
11+
Both languages allow destructuring assignment:
12+
13+
<CodeSplitter>
14+
<template #left>
15+
16+
```ts
17+
type Person = {
18+
firstName: string
19+
lastName: string
20+
}
21+
22+
const ada: Person = {
23+
firstName: "Ada",
24+
lastName: "Lovelace"
25+
}
26+
27+
let { firstName, lastName } = ada;
28+
// 👇 Rename requires explicit reassignment
29+
let { firstName: first, lastName: last } = ada;
30+
31+
console.log(firstName); // Ada
32+
console.log(lastName); // Lovelace
33+
34+
console.log(first); // Ada
35+
console.log(last); // Lovelace
36+
```
37+
38+
</template>
39+
<template #right>
40+
41+
```cs
42+
public record Person(
43+
string FirstName,
44+
string LastName
45+
);
46+
47+
var person = new Person(
48+
"Ada",
49+
"Lovelace"
50+
);
51+
52+
var (first, last) = person;
53+
54+
Console.WriteLine(first); // Ada
55+
Console.WriteLine(last); // Lovelace
56+
```
57+
58+
</template>
59+
</CodeSplitter>
60+
61+
Note a key difference here: in C#, it is possible to rename the properties on assignment.
62+
63+
Another key difference is that C# deconstruct is "all or nothing."
64+
65+
66+
<CodeSplitter>
67+
<template #left>
68+
69+
```ts
70+
// Valid to just take `firstName`
71+
let { firstName: fn } = ada;
72+
```
73+
74+
</template>
75+
<template #right>
76+
77+
```cs
78+
// ❌ Not valid
79+
var (fn) = person;
80+
81+
// ✅ Valid using a "discard"
82+
var (fn, _) = person;
83+
```
84+
85+
</template>
86+
</CodeSplitter>
87+
88+
That second parameter `_` on the C# side is called a "[discard](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/functional/discards)".
89+
90+
### Adding Destructuring to C# Classes
91+
92+
C# destructuring is by only "free" with C# `Tuple` and `record` types (for `record` types, it is only for the positional properties declared in the constructor).
93+
94+
For other types, it is necessary to add it manually:
95+
96+
```cs{5-11}
97+
public class Person { // 👈 Note: this is a `class`, not a `record`
98+
public string FirstName { get; set; }
99+
public string LastName { get; set; }
100+
// 👇 Manually added method using `out` parameters
101+
public void Deconstruct(
102+
out string firstName,
103+
out string lastName
104+
) {
105+
firstName = FirstName;
106+
lastName = LastName;
107+
}
108+
}
109+
110+
var ada = new Person {
111+
FirstName = "Ada",
112+
LastName = "Lovelace"
113+
};
114+
115+
var (firstName, lastName) = ada;
116+
117+
Console.WriteLine(firstName); // Ada
118+
Console.WriteLine(lastName); // Lovelace
119+
```
120+
121+
## Spread
122+
123+
Spread is a double-edged sword in JS, but is a powerful tool, nonetheless and C# only offers a small subset of the capabilities and only with collections.
124+
125+
### Collections
126+
127+
Both C# and JS support spread operations on collections:
128+
129+
<CodeSplitter>
130+
<template #left>
131+
132+
```ts
133+
let mine = ["apple", "banana"];
134+
let yours = ["orange", "grape"];
135+
136+
let ours = [...mine, ...yours];
137+
console.log(ours); // ["apple","banana","orange","grape"]
138+
```
139+
140+
</template>
141+
<template #right>
142+
143+
```cs
144+
var mine = new [] { "apple", "banana" };
145+
var yours = new [] { "orange", "grape" };
146+
147+
string[] ours = [.. mine, .. yours];
148+
Console.WriteLine(string.Join(", ", ours)); // "apple, banana, orange, grape"
149+
```
150+
151+
</template>
152+
153+
</CodeSplitter>
154+
155+
In C#, the `[.. enumerable]` is useful as shorthand when working with `Linq` as well (shorthand to materialize the list).
156+
157+
### Objects
158+
159+
JS takes this a step further and allows _objects_ to be spread.
160+
161+
<CodeSplitter>
162+
<template #left>
163+
164+
```ts
165+
let ada = {
166+
firstName: "Ada",
167+
lastName: "Lovelace"
168+
};
169+
170+
let charles = {
171+
firstName: "Charles",
172+
lastName: "Babbage",
173+
nickName: "Chuck"
174+
};
175+
176+
let adaClone = { ...ada };
177+
console.log(adaClone); // { firstName: "Ada", lastName: "Lovelace" }
178+
179+
let who = { ...ada, ...charles };
180+
console.log(who); // { firstName: "Charles", lastName: "Babbage", nickName: "Chuck" }}
181+
```
182+
183+
</template>
184+
<template #right>
185+
186+
```cs
187+
// No equivalent; need to manipulate the objects
188+
var ada = new {
189+
FirstName = "Ada",
190+
LastName = "Lovelace"
191+
};
192+
193+
var charles = new {
194+
FirstName = "Charles",
195+
LastName = "Babbage",
196+
Nickname = "Chuck"
197+
};
198+
199+
var adaClone = new {
200+
ada.FirstName,
201+
ada.LastName,
202+
};
203+
204+
Console.WriteLine(adaClone); // { FirstName = Ada, LastName = Lovelace }
205+
206+
var who = new {
207+
ada.FirstName,
208+
ada.LastName,
209+
charles.Nickname
210+
};
211+
212+
Console.WriteLine(who); // { FirstName = Ada, LastName = Lovelace, Nickname = Chuck }
213+
214+
```
215+
216+
</template>
217+
218+
</CodeSplitter>
219+
220+
::: info Why double-edged?
221+
There are a few reasons why I generally avoid spread operators with objects in JS. First is that it can make the real shape "opaque" and becomes a slippery slope in TypeScript where it can become very difficult to track down where a field is coming from. Second is that it is easy to override existing fields by accident. I generally use it sparingly for objects (usually when cloning) as I find it has the quality of making code harder to skim and comprehend since it then often requires an additional mental "stack push" to understand an underlying shape.
222+
:::

src/csharp/csharp-notebook.dib

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -834,3 +834,75 @@ var job1 = new Job("job1", Status.Completed);
834834
Console.WriteLine(JsonSerializer.Serialize(job1));
835835

836836
Console.WriteLine(JsonSerializer.Serialize(job1, options));
837+
838+
#!csharp
839+
840+
public record Person(string FirstName, string LastName);
841+
842+
var person = new Person("Ada", "Lovelace");
843+
844+
var (first, last) = person;
845+
846+
Console.WriteLine(first); // Ada
847+
Console.WriteLine(last); // Lovelace
848+
849+
#!csharp
850+
851+
public class Person {
852+
public string FirstName { get; set; }
853+
public string LastName { get; set; }
854+
// 👇 Manually added method
855+
public void Deconstruct(
856+
out string firstName,
857+
out string lastName
858+
) {
859+
firstName = FirstName;
860+
lastName = LastName;
861+
}
862+
}
863+
864+
var ada = new Person {
865+
FirstName = "Ada",
866+
LastName = "Lovelace"
867+
};
868+
869+
var (firstName, lastName) = ada;
870+
871+
Console.WriteLine(firstName); // Ada
872+
Console.WriteLine(lastName); // Lovelace
873+
874+
#!csharp
875+
876+
var mine = new [] { "apple", "banana" };
877+
var yours = new [] { "orange", "grape" };
878+
879+
string[] ours = [.. mine, .. yours];
880+
Console.WriteLine(string.Join(", ", ours)); // "apple, banana, orange, grape"
881+
882+
#!csharp
883+
884+
var ada = new {
885+
FirstName = "Ada",
886+
LastName = "Lovelace"
887+
};
888+
889+
var charles = new {
890+
FirstName = "Charles",
891+
LastName = "Babbage",
892+
Nickname = "Chuck"
893+
};
894+
895+
var adaClone = new {
896+
ada.FirstName,
897+
ada.LastName,
898+
};
899+
900+
Console.WriteLine(adaClone); // { FirstName = Ada, LastName = Lovelace }
901+
902+
var who = new {
903+
ada.FirstName,
904+
ada.LastName,
905+
charles.Nickname
906+
};
907+
908+
Console.WriteLine(who); // { FirstName = Ada, LastName = Lovelace, Nickname = Chuck }

src/typescript/js-notebook.dib

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!meta
22

3-
{"kernelInfo":{"defaultKernelName":"javascript","items":[{"aliases":[],"languageName":"javascript","name":"javascript"}]}}
3+
{"kernelInfo":{"defaultKernelName":"javascript","items":[{"name":"csharp","languageName":"C#","aliases":["c#","cs"]},{"name":"fsharp","languageName":"F#","aliases":["f#","fs"]},{"name":"html","languageName":"HTML"},{"name":"http","languageName":"HTTP"},{"name":"javascript","languageName":"javascript"},{"name":"mermaid","languageName":"Mermaid"},{"name":"pwsh","languageName":"PowerShell","aliases":["powershell"]},{"name":"value"}]}}
44

55
#!javascript
66

@@ -86,3 +86,30 @@ fn("Hello, World!");
8686

8787
var contacts = ["Allie", "Stella", "Carson"];
8888
contacts.forEach(fn)
89+
90+
#!javascript
91+
92+
let mine = ["apple", "banana"];
93+
let yours = ["orange", "grape"];
94+
95+
let ours = [...mine, ...yours];
96+
console.log(ours); // ["apple","banana","orange","grape"]
97+
98+
#!javascript
99+
100+
let ada = {
101+
firstName: "Ada",
102+
lastName: "Lovelace"
103+
};
104+
105+
let charles = {
106+
firstName: "Charles",
107+
lastName: "Babbage",
108+
nickName: "Chuck"
109+
};
110+
111+
let adaClone = { ...ada };
112+
console.log(adaClone); // { firstName: "Ada", lastName: "Lovelace" }
113+
114+
let who = { ...ada, ...charles };
115+
console.log(who); // { firstName: "Charles", lastName: "Babbage", nickName: "Chuck" }}

0 commit comments

Comments
 (0)