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