用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/Objective-Arts/lens-dist --skill csharp-depth命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
正在显示 SKILL.md
基于 SOC 职业分类
| name | csharp-depth |
| description | Deep C# expertise from C# in Depth |
Jon Skeet's core belief: Understand the language deeply, then let that understanding guide simple code. The goal isn't to use every feature—it's to know which feature fits each situation.
"The more you understand about how C# works, the simpler your code can be."
Deep knowledge enables simplicity. You don't need clever tricks when you understand the fundamentals.
The most fundamental distinction in C#. Get this wrong, and everything else breaks.
Value types (structs):
Reference types (classes):
// Value type: independent copies
int a = 5;
int b = a;
b = 10;
Console.WriteLine(a); // Still 5
// Reference type: shared object
var list1 = new List<int> { 1, 2, 3 };
var list2 = list1;
list2.Add(4);
Console.WriteLine(list1.Count); // 4 - same list!
Struct guidelines:
Enable nullable reference types. They catch null bugs at compile time.
#nullable enable
// Compiler tracks nullability
string name = "Alice"; // Non-null, guaranteed
string? nickname = null; // Nullable, explicit
void Greet(string name) // Caller must provide non-null
{
Console.WriteLine(name.Length); // Safe - can't be null
}
void GreetOptional(string? name)
{
// Must check before use
if (name is not null)
{
Console.WriteLine(name.Length);
}
}
Nullable patterns:
// Null-coalescing
string display = nickname ?? "No nickname";
// Null-coalescing assignment
nickname ??= "Default";
// Null-conditional
int? length = nickname?.Length;
// Null-forgiving (when you know better than compiler)
string definitelyNotNull = GetValueThatMightBeNull()!;
// Use sparingly - you're telling compiler to trust you
Modern C# pattern matching replaces verbose type checks and casts.
Not this:
if (obj is string)
{
string s = (string)obj;
Console.WriteLine(s.Length);
}
This:
if (obj is string s)
{
Console.WriteLine(s.Length);
}
Switch expressions (C# 8+):
string GetDescription(Shape shape) => shape switch
{
Circle { Radius: 0 } => "Point",
Circle { Radius: var r } => $"Circle with radius {r}",
Rectangle { Width: var w, Height: var h } when w == h => $"Square {w}x{w}",
Rectangle { Width: var w, Height: var h } => $"Rectangle {w}x{h}",
_ => "Unknown shape"
};
Property patterns:
// Nested property matching
if (person is { Address: { City: "London" } })
{
// Person lives in London
}
// C# 10+ simplified
if (person is { Address.City: "London" })
{
// Same thing, cleaner
}
Records are the right choice for immutable data types.
// Immutable by default, value equality, with-expressions, deconstruction
public record Person(string Name, int Age);
var alice = new Person("Alice", 30);
var older = alice with { Age = 31 }; // Creates new instance
// Value equality
var alice2 = new Person("Alice", 30);
Console.WriteLine(alice == alice2); // True
// Deconstruction
var (name, age) = alice;
Record structs (C# 10+):
// Value type record - best of both worlds
public readonly record struct Point(double X, double Y);
When to use records vs classes:
Covariance and contravariance are confusing until they click.
Covariance (out): Can use more derived type
// IEnumerable<out T> is covariant
IEnumerable<Animal> animals = new List<Dog>(); // OK - Dog is Animal
// Works because you only GET values out
foreach (Animal animal in animals) { }
Contravariance (in): Can use less derived type
// Action<in T> is contravariant
Action<Dog> dogAction = (Animal a) => Console.WriteLine(a.Name);
// Works because you only PUT values in
dogAction(new Dog());
Memory aid:
out = output = covariant = can substitute derivedin = input = contravariant = can substitute baseUnderstanding when LINQ executes is critical.
Deferred execution (most LINQ):
var query = numbers.Where(n => n > 5); // Nothing happens yet
// The filter runs when you iterate:
foreach (var n in query) { } // NOW it executes
Immediate execution:
var list = numbers.Where(n => n > 5).ToList(); // Executes now
var count = numbers.Count(n => n > 5); // Executes now
var first = numbers.First(n => n > 5); // Executes now
The multiple enumeration trap:
// BAD: Enumerates twice (or worse, source changed between)
IEnumerable<int> filtered = GetNumbers().Where(n => n > 5);
Console.WriteLine(filtered.Count()); // Enumerates
Console.WriteLine(filtered.Sum()); // Enumerates again
// GOOD: Materialize once
var filtered = GetNumbers().Where(n => n > 5).ToList();
Console.WriteLine(filtered.Count); // No enumeration
Console.WriteLine(filtered.Sum()); // No enumeration
Modern syntax for creating collections.
// Old way
int[] numbers = new int[] { 1, 2, 3 };
List<int> list = new List<int> { 1, 2, 3 };
// New way
int[] numbers = [1, 2, 3];
List<int> list = [1, 2, 3];
// Spread operator
int[] combined = [..first, ..second, 99];
// Works with any collection type
ImmutableArray<int> immutable = [1, 2, 3];
Span<int> span = [1, 2, 3];
// WRONG: All lambdas capture the same variable
var actions = new List<Action>();
for (int i = 0; i < 5; i++)
{
actions.Add(() => Console.WriteLine(i));
}
// All print 5!
// RIGHT: Capture loop variable by value
for (int i = 0; i < 5; i++)
{
int captured = i;
actions.Add(() => Console.WriteLine(captured));
}
// Prints 0, 1, 2, 3, 4
// Note: foreach fixed this in C# 5
foreach (var item in items)
{
actions.Add(() => Console.WriteLine(item)); // Works correctly
}
// DANGEROUS: Mutable struct
public struct MutablePoint
{
public int X;
public int Y;
public void Move(int dx, int dy) { X += dx; Y += dy; }
}
var point = new MutablePoint { X = 1, Y = 2 };
// This works as expected
point.Move(1, 1);
// But this doesn't!
var points = new List<MutablePoint> { point };
points[0].Move(1, 1); // Compiles but modifies a COPY
// points[0] unchanged!
// SAFE: Immutable struct
public readonly struct Point
{
public int X { get; init; }
public int Y { get; init; }
public Point Move(int dx, int dy) => new(X + dx, Y + dy);
}
// BOXING: Value type → object allocation
int x = 42;
object boxed = x; // Allocates on heap
// Avoid in hot paths:
// BAD
void Log(object value) => Console.WriteLine(value);
Log(42); // Boxing!
// GOOD
void Log<T>(T value) => Console.WriteLine(value);
Log(42); // No boxing
Before committing C# code, ask:
?, non-nullable guaranteed?| Scenario | Apply Skeet |
|---|---|
| Language edge cases, "why does this work?" | Yes |
| Generics, variance, type inference | Yes |
| LINQ behavior and optimization | Yes |
| Nullable reference types | Yes |
| Async/await behavior | Partially - see async |
| Performance optimization | Partially - see performance-specific resources |
"The more you understand about how C# works, the simpler your code can be." — Jon Skeet