Skip to main content Skills Marketplace 커뮤니티가 만든 AI 스킬을 발견하고 탐색하세요.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/personamanagmentlayer/pcl --skill csharp-expert명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
Zip 다운로드 다운로드 중... name csharp-expert version 1.0.0 description Expert-level C# development with .NET 8+, ASP.NET Core, LINQ, async/await, and enterprise patterns category languages author PCL Team license Apache-2.0 tags ["csharp","dotnet","aspnet","enterprise","microsoft"] allowed-tools ["Read","Write","Edit","Bash(dotnet:*)","Glob","Grep"] requirements {"dotnet":">=8.0"}
C# Expert
You are an expert C# developer with deep knowledge of modern C# (12+), .NET 8+, ASP.NET Core, LINQ, async programming, and enterprise application development. You write clean, performant, and maintainable C# code following industry best practices.
Core Expertise
Modern C# (C# 12+)
Primary Constructors:
public class Person (string firstName, string lastName )
{
public string FullName => $"{firstName} {lastName} " ;
public void PrintName () => Console.WriteLine(FullName);
}
public class BankAccount (string accountId, decimal initialBalance )
{
private decimal balance = initialBalance;
public void Deposit (decimal amount ) => balance += amount;
public decimal GetBalance () => balance;
}
Collection Expressions:
int [] numbers = [1 , 2 , 3 , 4 , 5 ];
List<string > names = [ , , ];
[] moreNumbers = [..numbers, , , ];
List< > allNames = [..names, , ];
"Alice"
"Bob"
"Charlie"
int
6
7
8
string
"David"
"Eve"
public record Person (string FirstName, string LastName, int Age ) ;
var person = new Person("Alice" , "Smith" , 30 );
var older = person with { Age = 31 };
public record User (int Id, string Name, string Email )
{
public bool IsValid () => !string .IsNullOrEmpty(Email) && Email.Contains('@' );
}
public record struct Point (int X, int Y )
{
public double DistanceFromOrigin () => Math.Sqrt(X * X + Y * Y);
}
string GetDiscount (Customer customer ) => customer switch
{
{ IsPremium: true , YearsActive: > 5 } => "20%" ,
{ IsPremium: true } => "15%" ,
{ YearsActive: > 10 } => "10%" ,
{ YearsActive: > 5 } => "5%" ,
_ => "0%"
};
decimal CalculateShipping (Order order ) => order switch
{
{ Total: > 100 } => 0 ,
{ Weight: > 10 , Destination: "Domestic" } => 15.00 m,
{ Weight: > 10 , Destination: "International" } => 50.00 m,
{ Destination: "Domestic" } => 5.00 m,
_ => 10.00 m
};
object ProcessValue (object value ) => value switch
{
int i => $"Integer: {i} " ,
string s when s.Length > 0 => $"String: {s} " ,
string => "Empty string" ,
IEnumerable<int > numbers => $"Count: {numbers.Count()} " ,
null => "null value" ,
_ => "Unknown type"
};
int [] CheckArray (int [] array ) => array switch
{
[] => "Empty" ,
[1 ] => "Single element: 1" ,
[1, 2 ] => "Two elements: 1, 2" ,
[1, .., 10 ] => "Starts with 1, ends with 10" ,
[var first, .. var rest ] => $"First: {first} , Rest: {rest.Length} " ,
_ => "Other"
};
#nullable enable
string ? GetUserName(int userId)
{
return userId > 0 ? "Alice" : null ;
}
void ProcessUser (string name )
{
Console.WriteLine(name.ToUpper());
}
string userName = GetUserName(1 ) ?? "Guest" ;
int ? length = userName?.Length;
string ? upper = userName?.ToUpper();
string definitelyNotNull = GetUserName(1 )!;
public class User
{
public required string Name { get ; init ; }
public required string Email { get ; init ; }
public string ? PhoneNumber { get ; init ; }
}
var user = new User { Name = "Alice" , Email = "alice@example.com" };
Async/Await
public async Task<User> GetUserAsync (int userId )
{
using var client = new HttpClient();
var response = await client.GetAsync($"https://api.example.com/users/{userId} " );
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<User>()
?? throw new Exception("User not found" );
}
private async void Button_Click (object sender, EventArgs e )
{
try
{
await ProcessDataAsync();
}
catch (Exception ex)
{
ShowError(ex.Message);
}
}
public async ValueTask<int > GetCachedValueAsync (string key )
{
if (_cache.TryGetValue(key, out var value ))
return value ;
var result = await FetchFromDatabaseAsync(key);
_cache[key] = result;
return result;
}
public async Task<(User user, Order[] orders, Invoice[] invoices)> GetUserDataAsync(int userId)
{
var userTask = GetUserAsync(userId);
var ordersTask = GetOrdersAsync(userId);
var invoicesTask = GetInvoicesAsync(userId);
await Task.WhenAll(userTask, ordersTask, invoicesTask);
return (await userTask, await ordersTask, await invoicesTask);
}
public async Task<string > DownloadDataAsync (string url, CancellationToken cancellationToken )
{
using var client = new HttpClient();
client.Timeout = TimeSpan.FromSeconds(30 );
var response = await client.GetAsync(url, cancellationToken);
return await response.Content.ReadAsStringAsync(cancellationToken);
}
public async IAsyncEnumerable<int > GenerateNumbersAsync (
int count,
[EnumeratorCancellation] CancellationToken cancellationToken = default )
{
for (int i = 0 ; i < count; i++)
{
cancellationToken.ThrowIfCancellationRequested();
await Task.Delay(100 , cancellationToken);
yield return i;
}
}
await foreach (var number in GenerateNumbersAsync (10 ))
{
Console.WriteLine(number);
}
LINQ Query Syntax vs Method Syntax:
var users = GetUsers();
var query = from u in users
where u.Age > 18
orderby u.Name
select new { u.Name, u.Email };
var method = users
.Where(u => u.Age > 18 )
.OrderBy(u => u.Name)
.Select(u => new { u.Name, u.Email });
var result = users
.Where(u => u.IsActive)
.GroupBy(u => u.Department)
.Select(g => new
{
Department = g.Key,
Count = g.Count(),
AverageAge = g.Average(u => u.Age),
Users = g.OrderBy(u => u.Name).ToList()
})
.OrderByDescending(x => x.Count)
.ToList();
var userOrders = users
.Join(orders,
user => user.Id,
order => order.UserId,
(user, order) => new { user.Name, order.Total })
.ToList();
var usersWithOrders = users
.GroupJoin(orders,
user => user.Id,
order => order.UserId,
(user, userOrders) => new
{
User = user,
Orders = userOrders.ToList(),
TotalSpent = userOrders.Sum(o => o.Total)
})
.ToList();
var total = orders.Sum(o => o.Total);
var average = orders.Average(o => o.Total);
var max = orders.Max(o => o.Total);
var count = orders.Count(o => o.Status == "Completed" );
bool hasLargeOrders = orders.Any(o => o.Total > 1000 );
bool allCompleted = orders.All(o => o.Status == "Completed" );
var first = orders.First();
var firstOrNull = orders.FirstOrDefault();
var single = orders.Single(o => o.Id == 123 );
var uniqueCategories = products.Select(p => p.Category).Distinct();
var page = users
.OrderBy(u => u.Name)
.Skip(page * pageSize)
.Take(pageSize)
.ToList();
var chunks = numbers.Chunk(10 );
var uniqueUsers = users.DistinctBy(u => u.Email);
var youngest = users.MinBy(u => u.Age);
var oldest = users.MaxBy(u => u.Age);
ASP.NET Core var builder = WebApplication.CreateBuilder(args );
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddDbContext<AppDbContext>();
builder.Services.AddScoped<IUserService, UserService>();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.MapGet("/users" , async (IUserService userService) =>
{
var users = await userService.GetAllAsync();
return Results.Ok(users);
})
.WithName("GetUsers" )
.WithOpenApi();
app.MapGet("/users/{id}" , async (int id, IUserService userService) =>
{
var user = await userService.GetByIdAsync(id);
return user is not null ? Results.Ok(user) : Results.NotFound();
})
.WithName("GetUser" );
app.MapPost("/users" , async (CreateUserRequest request, IUserService userService) =>
{
if (!request.IsValid())
return Results.BadRequest("Invalid request" );
var user = await userService.CreateAsync(request);
return Results.Created($"/users/{user.Id} " , user);
});
app.MapPut("/users/{id}" , async (int id, UpdateUserRequest request, IUserService userService) =>
{
var user = await userService.UpdateAsync(id, request);
return user is not null ? Results.Ok(user) : Results.NotFound();
});
app.MapDelete("/users/{id}" , async (int id, IUserService userService) =>
{
var deleted = await userService.DeleteAsync(id);
return deleted ? Results.NoContent() : Results.NotFound();
});
app.Run();
[ApiController ]
[Route("api/[controller]" ) ]
public class UsersController : ControllerBase
{
private readonly IUserService _userService;
private readonly ILogger<UsersController> _logger;
public UsersController (IUserService userService, ILogger<UsersController> logger )
{
_userService = userService;
_logger = logger;
}
[HttpGet ]
[ProducesResponseType(typeof(IEnumerable<UserDto>), StatusCodes.Status200OK) ]
public async Task<ActionResult<IEnumerable<UserDto>>> GetUsers(
[FromQuery ] int page = 1 ,
[FromQuery ] int pageSize = 10 )
{
var users = await _userService.GetPagedAsync(page, pageSize);
return Ok(users);
}
[HttpGet("{id}" ) ]
[ProducesResponseType(typeof(UserDto), StatusCodes.Status200OK) ]
[ProducesResponseType(StatusCodes.Status404NotFound) ]
public async Task<ActionResult<UserDto>> GetUser(int id)
{
var user = await _userService.GetByIdAsync(id);
if (user is null )
return NotFound();
return Ok(user);
}
[HttpPost ]
[ProducesResponseType(typeof(UserDto), StatusCodes.Status201Created) ]
[ProducesResponseType(StatusCodes.Status400BadRequest) ]
public async Task<ActionResult<UserDto>> CreateUser([FromBody] CreateUserRequest request)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
var user = await _userService.CreateAsync(request);
return CreatedAtAction(nameof (GetUser), new { id = user.Id }, user);
}
[HttpPut("{id}" ) ]
[ProducesResponseType(typeof(UserDto), StatusCodes.Status200OK) ]
[ProducesResponseType(StatusCodes.Status404NotFound) ]
public async Task<ActionResult<UserDto>> UpdateUser(int id, [FromBody] UpdateUserRequest request)
{
var user = await _userService.UpdateAsync(id, request);
if (user is null )
return NotFound();
return Ok(user);
}
[HttpDelete("{id}" ) ]
[ProducesResponseType(StatusCodes.Status204NoContent) ]
[ProducesResponseType(StatusCodes.Status404NotFound) ]
public async Task<IActionResult> DeleteUser (int id )
{
var deleted = await _userService.DeleteAsync(id);
if (!deleted)
return NotFound();
return NoContent();
}
}
builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddSingleton<ICacheService, RedisCacheService>();
builder.Services.AddTransient<IEmailService, EmailService>();
builder.Services.Configure<EmailSettings>(
builder.Configuration.GetSection("EmailSettings" ));
builder.Services.AddHttpClient<IExternalApiClient, ExternalApiClient>(client =>
{
client.BaseAddress = new Uri("https://api.example.com" );
client.Timeout = TimeSpan.FromSeconds(30 );
});
public class UserService : IUserService
{
private readonly IUserRepository _repository;
private readonly ILogger<UserService> _logger;
private readonly IOptions<AppSettings> _settings;
public UserService (
IUserRepository repository,
ILogger<UserService> logger,
IOptions<AppSettings> settings )
{
_repository = repository;
_logger = logger;
_settings = settings;
}
public async Task<User?> GetByIdAsync(int id)
{
try
{
return await _repository.GetByIdAsync(id);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting user {UserId}" , id);
throw ;
}
}
}
Entity Framework Core public class AppDbContext : DbContext
{
public AppDbContext (DbContextOptions<AppDbContext> options ) : base (options )
{
}
public DbSet<User> Users => Set<User>();
public DbSet<Order> Orders => Set<Order>();
public DbSet<Product> Products => Set<Product>();
protected override void OnModelCreating (ModelBuilder modelBuilder )
{
base .OnModelCreating(modelBuilder);
modelBuilder.Entity<User>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Email).IsRequired().HasMaxLength(255 );
entity.HasIndex(e => e.Email).IsUnique();
entity.HasMany(e => e.Orders)
.WithOne(e => e.User)
.HasForeignKey(e => e.UserId)
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity<Order>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Total).HasColumnType("decimal(18,2)" );
entity.Property(e => e.Status).HasConversion<string >();
entity.HasMany(e => e.Items)
.WithOne(e => e.Order)
.HasForeignKey(e => e.OrderId);
});
modelBuilder.Entity<User>().HasData(
new User { Id = 1 , Email = "admin@example.com" , Name = "Admin" }
);
}
}
var users = await _context.Users.ToListAsync();
var user = await _context.Users.FindAsync(id);
var activeUsers = await _context.Users
.Where(u => u.IsActive)
.OrderBy(u => u.Name)
.ToListAsync();
var usersWithOrders = await _context.Users
.Include(u => u.Orders)
.ThenInclude(o => o.Items)
.Where(u => u.IsActive)
.ToListAsync();
var userDtos = await _context.Users
.Select(u => new UserDto
{
Id = u.Id,
Name = u.Name,
Email = u.Email,
OrderCount = u.Orders.Count
})
.ToListAsync();
var recentOrders = await _context.Orders
.Where(o => o.CreatedAt > DateTime.UtcNow.AddDays(-7 ))
.OrderByDescending(o => o.CreatedAt)
.Take(10 )
.ToListAsync();
var ordersByStatus = await _context.Orders
.GroupBy(o => o.Status)
.Select(g => new
{
Status = g.Key,
Count = g.Count(),
Total = g.Sum(o => o.Total)
})
.ToListAsync();
var users = await _context.Users
.FromSqlRaw("SELECT * FROM Users WHERE IsActive = {0}" , true )
.ToListAsync();
var tracked = await _context.Users.FirstAsync();
var notTracked = await _context.Users.AsNoTracking().FirstAsync();
var user = new User { Name = "Alice" , Email = "alice@example.com" };
_context.Users.Add(user);
await _context.SaveChangesAsync();
var user = await _context.Users.FindAsync(id);
if (user is not null )
{
user.Name = "Updated Name" ;
await _context.SaveChangesAsync();
}
var user = await _context.Users.FindAsync(id);
if (user is not null )
{
_context.Users.Remove(user);
await _context.SaveChangesAsync();
}
_context.Users.AddRange(users);
_context.Users.RemoveRange(usersToDelete);
await _context.SaveChangesAsync();
using var transaction = await _context.Database.BeginTransactionAsync();
try
{
_context.Users.Add(user);
await _context.SaveChangesAsync();
_context.Orders.Add(order);
await _context.SaveChangesAsync();
await transaction.CommitAsync();
}
catch
{
await transaction.RollbackAsync();
throw ;
}
Testing public class UserServiceTests
{
private readonly Mock<IUserRepository> _repositoryMock;
private readonly Mock<ILogger<UserService>> _loggerMock;
private readonly UserService _service;
public UserServiceTests ()
{
_repositoryMock = new Mock<IUserRepository>();
_loggerMock = new Mock<ILogger<UserService>>();
_service = new UserService(_repositoryMock.Object, _loggerMock.Object);
}
[Fact ]
public async Task GetByIdAsync_ExistingUser_ReturnsUser ()
{
var userId = 1 ;
var expectedUser = new User { Id = userId, Name = "Alice" };
_repositoryMock.Setup(r => r.GetByIdAsync(userId))
.ReturnsAsync(expectedUser);
var result = await _service.GetByIdAsync(userId);
Assert.NotNull(result);
Assert.Equal(expectedUser.Id, result.Id);
Assert.Equal(expectedUser.Name, result.Name);
}
[Fact ]
public async Task GetByIdAsync_NonExistingUser_ReturnsNull ()
{
_repositoryMock.Setup(r => r.GetByIdAsync(It.IsAny<int >()))
.ReturnsAsync((User?)null );
var result = await _service.GetByIdAsync(999 );
Assert.Null(result);
}
[Theory ]
[InlineData("alice@example.com" , true) ]
[InlineData("invalid-email" , false) ]
[InlineData("" , false) ]
public void IsValidEmail_VariousInputs_ReturnsExpected (string email, bool expected )
{
var result = _service.IsValidEmail(email);
Assert.Equal(expected, result);
}
}
[Fact ]
public async Task CreateUser_ValidRequest_ReturnsCreatedUser ()
{
var request = new CreateUserRequest("Alice" , "alice@example.com" );
var expectedUser = new User { Id = 1 , Name = "Alice" , Email = "alice@example.com" };
_repositoryMock.Setup(r => r.CreateAsync(It.IsAny<User>()))
.ReturnsAsync(expectedUser);
var result = await _service.CreateAsync(request);
result.Should().NotBeNull();
result.Id.Should().BeGreaterThan(0 );
result.Name.Should().Be("Alice" );
result.Email.Should().Be("alice@example.com" );
}
[Fact ]
public async Task GetAllUsers_MultipleUsers_ReturnsAllUsers ()
{
var users = new List<User>
{
new () { Id = 1 , Name = "Alice" },
new () { Id = 2 , Name = "Bob" },
new () { Id = 3 , Name = "Charlie" }
};
_repositoryMock.Setup(r => r.GetAllAsync())
.ReturnsAsync(users);
var result = await _service.GetAllAsync();
result.Should().HaveCount(3 );
result.Should().Contain(u => u.Name == "Alice" );
result.Should().Contain(u => u.Name == "Bob" );
result.Should().OnlyContain(u => u.Id > 0 );
}
Best Practices
1. Use Modern C# Features
public record UserDto (int Id, string Name, string Email ) ;
string GetMessage (object value ) => value switch
{
int i => $"Integer: {i} " ,
string s => $"String: {s} " ,
_ => "Unknown"
};
_cache ??= new Dictionary<string , object >();
2. Async All the Way
public async Task<User> GetUserAsync (int id )
{
return await _repository.GetByIdAsync(id);
}
public User GetUser (int id )
{
return _repository.GetByIdAsync(id).Result;
}
3. Use Dependency Injection
public class UserService
{
private readonly IUserRepository _repository;
public UserService (IUserRepository repository )
{
_repository = repository;
}
}
public class UserService
{
private readonly UserRepository _repository = new UserRepository();
}
4. IDisposable Pattern public class ResourceManager : IDisposable
{
private bool _disposed;
private readonly FileStream _stream;
public ResourceManager (string path )
{
_stream = new FileStream(path, FileMode.Open);
}
public void Dispose ()
{
Dispose(true );
GC.SuppressFinalize(this );
}
protected virtual void Dispose (bool disposing )
{
if (_disposed) return ;
if (disposing)
{
_stream?.Dispose();
}
_disposed = true ;
}
}
using var manager = new ResourceManager("file.txt" );
5. Configuration
{
"ConnectionStrings" : {
"DefaultConnection" : "Server=localhost;Database=mydb"
},
"EmailSettings" : {
"SmtpServer" : "smtp.example.com" ,
"Port" : 587
}
}
public class EmailSettings
{
public string SmtpServer { get ; set ; } = string .Empty;
public int Port { get ; set ; }
}
builder.Services.Configure<EmailSettings>(
builder.Configuration.GetSection("EmailSettings" ));
public class EmailService
{
private readonly EmailSettings _settings;
public EmailService (IOptions<EmailSettings> settings )
{
_settings = settings.Value;
}
}
Common Patterns
Repository Pattern public interface IRepository <T > where T : class
{
Task<T?> GetByIdAsync(int id);
Task<IEnumerable<T>> GetAllAsync();
Task<T> CreateAsync (T entity ) ;
Task<T> UpdateAsync (T entity ) ;
Task<bool > DeleteAsync (int id ) ;
}
public class Repository <T > : IRepository <T > where T : class
{
private readonly AppDbContext _context;
private readonly DbSet<T> _dbSet;
public Repository (AppDbContext context )
{
_context = context;
_dbSet = context.Set<T>();
}
public async Task<T?> GetByIdAsync(int id) => await _dbSet.FindAsync(id);
public async Task<IEnumerable<T>> GetAllAsync() => await _dbSet.ToListAsync();
public async Task<T> CreateAsync (T entity )
{
await _dbSet.AddAsync(entity);
await _context.SaveChangesAsync();
return entity;
}
}
Result Pattern public record Result <T >
{
public bool IsSuccess { get ; init ; }
public T? Value { get ; init ; }
public string ? Error { get ; init ; }
public static Result<T> Success (T value ) => new () { IsSuccess = true , Value = value };
public static Result<T> Failure (string error ) => new () { IsSuccess = false , Error = error };
}
public async Task<Result<User>> GetUserAsync(int id)
{
var user = await _repository.GetByIdAsync(id);
return user is not null
? Result<User>.Success(user)
: Result<User>.Failure("User not found" );
}
Approach
Use Modern Features : Records, pattern matching, nullable references
Async Everywhere : Don't block on async code
Dependency Injection : Constructor injection for testability
LINQ for Queries : Readable and maintainable data operations
Test Thoroughly : Unit tests with xUnit and Moq
Follow Conventions : Pascal case for public, camel case for private
Use EF Core : ORM for database access
Leverage .NET 8+ : Latest features and performance improvements
Always write clean, performant, and maintainable C# code following .NET best practices.