최상위 실행문이 이제야 완성단계에 이른 것 같습니다.
이거 좀 유용할 거 같은데요.
agent mode나 mcp를 사용할 때 no project로 코드 작성하고 실행하는 게 좋더라고요.
드디어 파이썬과 비슷한 경험을 할 수 있겠구나! 싶었지만… 아무래도 딜레이를 보니 여전히 컴파일 과정을 거치긴 하나 봅니다.
임시 디렉터리에 프로젝트를 저장하는 기능을 제공하던 과거의 Visual Studio Express Edition 시리즈에서 출발해서, 제가 지금 애용하는 LINQPad까지 생각해보면 이런 ad-hoc 코딩을 무척 좋아했었는데, 드디어 마침표가 찍히는 느낌이네요! 세션 영상을 공유해주셔서 감사합니다!
올려주신 내용이 좀 더 구체적으로 문서화된 것이 있나 해서 찾아보니 역시 reddit에 구체적인 내용이 있네요!
https://www.reddit.com/r/rust/comments/1kwjmjm/dotnet_10_introduces_implicit_projects_with_a/
그리고 이 기능을 implicit project (암시적 프로젝트)라고 부르는 것 같습니다.
본래 C#에서는 '#'으로 시작하는 것이 매크로 전처리기 밖에 없었던 것 같은데, 이 기능을 위해서 문법이 확장된 모양입니다. 파일의 시작을 다음과 같이 구성할 수 있습니다.
#!/usr/bin/dotnet run
#:sdk Microsoft.NET.Sdk.Web
제일 첫 줄은 Shebang으로 배시 셸 스크립트나 파워셸 스크립트 첫 줄에 sh, bash, zsh가 이 파일을 실행 파일로 다루려 할 때 해당 스크립트를 어느 인터프리터와 연결시킬지 결정하는 부분입니다. '#'을 전처리 기호로 쓴 것은 그래서 굉장히 영리한 선택이라는 생각이 듭니다 ㅎㅎ
이런 특성 덕분에 아래처럼 리눅스 명령어를 실행하면 .NET SDK 10 버전이 설치되어있기만 해도 바로 셸 파일처럼 닷넷 프로그램을 쓸 수 있습니다.
chmod +x ./app.cs
./app.cs
그리고 그 다음에 오는 라인들은 CSPROJ의 핵심 설정들 중 중요한 설정들을 편하게 지정할 수 있게 되어있었습니다. 덕분에 implicit project를 이용해서 ASP .NET Core Web API 서버도 손쉽게 만들 수 있습니다.
제가 요즈음 ASP .NET Core 기반 SSE MCP 서버를 만들어보고 있던 참이어서, LINQPad로 되어있던 예제를 그대로 옮겨올 수 있는지 테스트해봤는데 아주 훌륭하게 이식이 됩니다.
#!/usr/bin/dotnet run
#:sdk Microsoft.NET.Sdk.Web
#:package ModelContextProtocol@0.2.0-preview*
#:package ModelContextProtocol.AspNetCore@0.2.0-preview*
#:package OpenTelemetry.Exporter.OpenTelemetryProtocol@1.12.*
#:package OpenTelemetry.Extensions.Hosting@1.12.*
#:package OpenTelemetry.Instrumentation.AspNetCore@1.12.*
#:package OpenTelemetry.Instrumentation.Http@1.12.*
// Cloudflare로 tunnel을 만들기 위해서 cloudflared를 사용하면 편리합니다.
// winget install --id Cloudflare.cloudflared
// cloudflared tunnel --url http://localhost:5000
#if LINQPAD
var args = Environment.GetCommandLineArgs().Skip(1).ToArray();
#endif
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using ModelContextProtocol.Server;
using OpenTelemetry;
using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;
using System.ComponentModel;
using System.Threading.Tasks;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMcpServer()
.WithHttpTransport()
.WithTools<EchoTool>()
.WithTools<EchoTool2>()
.WithTools<SampleLlmTool>();
builder.Services.AddOpenTelemetry()
.WithTracing(b => b.AddSource("*")
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation())
.WithMetrics(b => b.AddMeter("*")
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation())
.WithLogging()
.UseOtlpExporter();
var app = builder.Build();
app.MapMcp();
app.Run();
[McpServerToolType]
public sealed class EchoTool
{
[McpServerTool, Description("Echoes the input back to the client.")]
public static string Echo(string message)
{
return "hello " + message;
}
}
[McpServerToolType]
public sealed class EchoTool2
{
[McpServerTool, Description("Echoes the input back to the client with Korean.")]
public static string Echo2(string message)
{
return "안녕! " + message;
}
}
[McpServerToolType]
public sealed class SampleLlmTool
{
[McpServerTool(Name = "sampleLLM"), Description("Samples from an LLM using MCP's sampling feature")]
public static async Task<string> SampleLLM(
IMcpServer thisServer,
[Description("The prompt to send to the LLM")] string prompt,
[Description("Maximum number of tokens to generate")] int maxTokens,
CancellationToken cancellationToken)
{
ChatMessage[] messages =
[
new(ChatRole.System, "You are a helpful test server."),
new(ChatRole.User, prompt),
];
ChatOptions options = new()
{
MaxOutputTokens = maxTokens,
Temperature = 0.7f,
};
var samplingResponse = await thisServer.AsSamplingChatClient().GetResponseAsync(messages, options, cancellationToken);
return $"LLM sampling result: {samplingResponse}";
}
}
그리고 이렇게 만든 첫 파일을 dotnet project convert <app.cs 파일 경로> 명령어로 실행하면 Grow up 이라는 기능이 호출되어 일반적인 .NET SDK 프로젝트로 전환하는 것까지 완결성있게 처리되어 제가 앞에서 이야기했던 것처럼 빠른 프로토타이핑에서 시작하여 완성된 프로젝트로 전환하는 경험까지 구현되는 것이어서 마음에 쏙 듭니다. ![]()
아직 .NET 10이 프리뷰 버전이지만 그럼에도 지금부터 사용할 만한 강력한 동인이 저에게는 새롭게 생긴것 같습니다. ㅎㅎ
MCP 때문에 클로드 맥스 결재했는데 매우 훌륭한 생태계더라고요.
Avalonia도 당연히 테스트해볼 수 있을 것이라 생각했는데 아주 잘 작동합니다. ![]()
이렇게 만들면 GUI 애플리케이션이면서, Kestrel을 백엔드로 세울 수 있고, 크로스 플랫폼을 고려해서 리눅스, macOS에서도 동일한 GUI를 제공할 수 있지 않을까 기대가 됩니다.
#!/usr/bin/dotnet run
#:sdk Microsoft.NET.SDK.Web
#:package Avalonia.Desktop@11.*
#:package Avalonia.Fonts.Inter@11.*
#:package Avalonia.ReactiveUI@11.*
#:package Avalonia.Themes.Fluent@11.*
#:package Lemon.Hosting.AvaloniauiDesktop@1.*
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Data;
using Avalonia.Layout;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using Lemon.Hosting.AvaloniauiDesktop;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using ReactiveUI;
using System.Runtime.InteropServices;
using Avalonia.Themes.Fluent;
#if LINQPAD
var args = Environment.GetCommandLineArgs().Skip(1).ToArray();
Util.NewProcess = true;
#endif
var builder = Host.CreateApplicationBuilder();
builder.Configuration
.AddCommandLine(args)
.AddEnvironmentVariables()
.AddInMemoryCollection();
builder.Logging
.AddConsole();
builder.Services.AddAvaloniauiDesktopApplication<App>(
b => b.UsePlatformDetect().WithInterFont().LogToTrace());
builder.Services.AddMainWindow<MainWindow, MainWindowViewModel>();
var app = builder.Build();
await app.RunAvaloniauiApplication<MainWindow>(args,
RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? ShutdownMode.OnExplicitShutdown : ShutdownMode.OnLastWindowClose);
public sealed class App : Application
{
public App(IServiceProvider sp)
=> this.Styles.Add(new FluentTheme(sp));
}
public sealed class MainWindow : Window
{
public MainWindow(MainWindowViewModel viewModel)
{
DataContext = viewModel;
Title = "No XAML UI";
Width = 400;
Height = 300;
var button = new Button
{
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
};
button.Bind(Button.ContentProperty, new Binding { Path = nameof(viewModel.ButtonCaption), });
button.Bind(Button.CommandProperty, new Binding { Path = nameof(viewModel.ButtonAction), });
Content = new Grid
{
Children =
{
button,
}
};
}
}
public sealed class MainWindowViewModel : ViewModelBase
{
private int _count;
private string _buttonCaption = "Hello";
public string ButtonCaption
{
get => _buttonCaption;
set => this.RaiseAndSetIfChanged(ref _buttonCaption, value);
}
public void ButtonAction() => ButtonCaption = $"Clicked {++_count} time(s)!";
}
public abstract class ViewModelBase : ReactiveObject { }
magic-commands 랑은 또 다른 전처리자의 확장이었군요
닷넷10 새로운것 하나하나가 엄청 파격적이예요 걱정반기대반…

누겟에 패키지가져가는 방법만 8개 적혀있을 생각하니 어질어질합니다.
슈뱅이 REPL 환경에서 쓸 수 있다는 점은 처음 알았네요.
발표 시점을 보니, 닷넷 인터렉티브에서 사용하던 것을 좀 더 확장한 듯 보입니다.
찾아보니까 옛날에도 csx라는게 있었네요.
공식적으로는 폐기되었는지 이후에는 언급이 잘 안보이지만 비공식적으로는 쓰이는 듯 합니다.
이 프로젝트 처럼요.

