No projects just C# with `dotnet run app.cs` | DEM518

최상위 실행문이 이제야 완성단계에 이른 것 같습니다.

8개의 좋아요

이거 좀 유용할 거 같은데요.

agent mode나 mcp를 사용할 때 no project로 코드 작성하고 실행하는 게 좋더라고요.

5개의 좋아요

드디어 파이썬과 비슷한 경험을 할 수 있겠구나! 싶었지만… 아무래도 딜레이를 보니 여전히 컴파일 과정을 거치긴 하나 봅니다.

5개의 좋아요

임시 디렉터리에 프로젝트를 저장하는 기능을 제공하던 과거의 Visual Studio Express Edition 시리즈에서 출발해서, 제가 지금 애용하는 LINQPad까지 생각해보면 이런 ad-hoc 코딩을 무척 좋아했었는데, 드디어 마침표가 찍히는 느낌이네요! 세션 영상을 공유해주셔서 감사합니다!

4개의 좋아요

올려주신 내용이 좀 더 구체적으로 문서화된 것이 있나 해서 찾아보니 역시 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 프로젝트로 전환하는 것까지 완결성있게 처리되어 제가 앞에서 이야기했던 것처럼 빠른 프로토타이핑에서 시작하여 완성된 프로젝트로 전환하는 경험까지 구현되는 것이어서 마음에 쏙 듭니다. :smiley:

아직 .NET 10이 프리뷰 버전이지만 그럼에도 지금부터 사용할 만한 강력한 동인이 저에게는 새롭게 생긴것 같습니다. ㅎㅎ

8개의 좋아요

MCP 때문에 클로드 맥스 결재했는데 매우 훌륭한 생태계더라고요.

3개의 좋아요

Avalonia도 당연히 테스트해볼 수 있을 것이라 생각했는데 아주 잘 작동합니다. :smiley:

이렇게 만들면 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 { }
5개의 좋아요

magic-commands 랑은 또 다른 전처리자의 확장이었군요

닷넷10 새로운것 하나하나가 엄청 파격적이예요 걱정반기대반…

image

누겟에 패키지가져가는 방법만 8개 적혀있을 생각하니 어질어질합니다.

4개의 좋아요

슈뱅이 REPL 환경에서 쓸 수 있다는 점은 처음 알았네요.

발표 시점을 보니, 닷넷 인터렉티브에서 사용하던 것을 좀 더 확장한 듯 보입니다.

1개의 좋아요

찾아보니까 옛날에도 csx라는게 있었네요.

공식적으로는 폐기되었는지 이후에는 언급이 잘 안보이지만 비공식적으로는 쓰이는 듯 합니다.

이 프로젝트 처럼요.

6개의 좋아요