今日已更新 317 条资讯 | 累计 37222 条内容
关于我们

标签:#csharp

找到 63 篇相关文章

开发者

Sealed Isn't a Restriction, It's a Promise

Leaving a class open to inheritance is a design decision, not a default you can ignore. The core idea An unsealed class is a promise: every virtual member can be overridden without breaking what the class guarantees. Most classes never meant to make that promise. They're just unsealed by default, because that's what class gives you unless you say otherwise. Common mistake: treating sealed as "I don't want to think about subclassing" rather than "this type's invariants would break if someone could." One override breaks the promise Here's the promise, a BankAccount that refuses to go negative: public class BankAccount { public decimal Balance { get ; protected set ; } public virtual void Withdraw ( decimal amount ) { if ( amount > Balance ) throw new InvalidOperationException (); Balance -= amount ; } } And here's the override that breaks it: public class RiskyAccount : BankAccount { public override void Withdraw ( decimal amount ) { Balance -= amount ; // no check } } Nothing here is exotic. It compiles cleanly, and RiskyAccount is a perfectly legal BankAccount as far as the type system is concerned. Open one with a balance of 100 and withdraw 500: BankAccount account = new RiskyAccount ( 100m ); account . Withdraw ( 500m ); Console . WriteLine ( $"Balance: { account . Balance : F2 } " ); Real dotnet run output: Balance: -400.00 The check on the left never ran. virtual was an open invitation, and RiskyAccount took it. Sealing turns a silent bug into a compile error Without sealed , the code above compiles and produces a wrong answer at runtime; nothing points you at the problem until it's already in production. With sealed , the same mistake becomes something the compiler catches before the code ever runs: public sealed class BankAccount { public decimal Balance { get ; protected set ; } public void Withdraw ( decimal amount ) { if ( amount > Balance ) throw new InvalidOperationException (); Balance -= amount ; } } public class RiskyAccount : BankAccount { } // error

2026-08-29 原文 →
AI 资讯

How to Set Excel Cell Backgrounds in C#

Customizing cell backgrounds in Excel is one of the fastest ways to transform a plain data dump into a professional, scannable report. Whether you need to highlight headers, flag key metrics, or add visual polish to dashboards, the Free Spire.XLS for .NET library makes it easy to apply solid fills , texture patterns , and gradient effects programmatically. In this guide, you'll learn how to implement each style with concise C# examples. Prerequisites Install the library via NuGet Package Manager: Install-Package FreeSpire.XLS Then add the required namespaces to your project: using Spire.Xls ; using System.Drawing ; 1. Solid Fill (Flat Background) The solid fill is the most common background type—ideal for headers, totals, or status-based highlighting. using ( Workbook workbook = new Workbook ()) { Worksheet sheet = workbook . Worksheets [ 0 ]; CellRange cell = sheet . Range [ "B2" ]; cell . Text = "Solid Background" ; // Solid fill requires the pattern to be explicitly set to Solid cell . Style . FillPattern = ExcelPatternType . Solid ; cell . Style . Color = Color . LightGreen ; workbook . SaveToFile ( "CellSolidColor.xlsx" , ExcelVersion . Version2016 ); } ⚠️ Crucial : Always set FillPattern to ExcelPatternType.Solid before assigning a color. If omitted, the color change will be ignored. 2. Texture Fill (Pattern Overlay) Texture fills overlay a repeating pattern (e.g., brick, checker, or angle) over a base color. They're perfect for subtly distinguishing data categories without overwhelming the reader. using ( Workbook workbook = new Workbook ()) { Worksheet sheet = workbook . Worksheets [ 0 ]; CellRange cell = sheet . Range [ "B2" ]; cell . Text = "Texture Background" ; // Angle texture pattern cell . Style . FillPattern = ExcelPatternType . Angle ; cell . Style . Color = Color . LightGray ; // Base background color cell . Style . PatternColor = Color . Beige ; // Pattern overlay color workbook . SaveToFile ( "CellPattern.xlsx" , ExcelVersion . Version2016 ); } N

2026-08-26 原文 →
AI 资讯

.NET 10 NU1015: Fix PackageReference Without Version Restore Failures

.NET 10 NU1015 turns a PackageReference without a version into a restore error. I like the stricter default because an unbounded direct dependency can quietly resolve the lowest package version. The catch is that versionless XML is also the correct shape for NuGet Central Package Management (CPM). A mechanical “add Version everywhere” repair can undo the policy your repository intended to enforce. I use a simple split: first decide who owns the version, then make restore prove the answer. Why .NET 10 NU1015 stops the build Before .NET 10, NuGet reported NU1604 when a direct reference had no inclusive lower bound. Restore could continue and select the lowest version available from the configured sources. Starting with .NET 10, the same mistake produces NU1015 and restore fails. Microsoft documents this as a stable behavioral change in the .NET 10 compatibility guidance . Here is the ambiguous project entry: <ItemGroup> <PackageReference Include= "Demo.Greeting" /> </ItemGroup> If this is a normal direct reference, the project is missing its version. If CPM is active, the project is correct and the version should live elsewhere. The NU1015 diagnostic reference calls out a common failure mode: a project that expected CPM was copied into a location where CPM is disabled or its props file is no longer discovered. That distinction matters more than silencing the error. It tells me whether the project file or the repository-level package policy is broken. The timing can be misleading. An SDK upgrade may expose an old direct reference that had always relied on lowest-version resolution, while a repository move may break a previously valid CPM import. I inspect the failing project's evaluated inputs, nearby props files, and recent path changes before editing package metadata. That keeps a restore migration from turning into an accidental package-management migration. Fix the owner, not only the XML For a direct reference, I add an explicit version: <PackageReference Include=

2026-08-24 原文 →
AI 资讯

.NET 10 JSON Console Logging: Stop Parsing State.Message

The .NET 10 JSON console logging change is small enough to miss during an upgrade: the formatted message still exists, but a typical record no longer duplicates it at State.Message . A collector, script, or snapshot test that reads only that nested property can start returning null while the application continues logging normally. I treat console JSON as a schema whenever another process parses it. That means a runtime upgrade deserves a contract test, not just a visual check in a terminal. The practical fix is to read the top-level Message , keep State for structured values, and retain a narrow fallback for older records. Why .NET 10 JSON console logging breaks nested-message parsers Before .NET 10, a normal AddJsonConsole record commonly repeated the rendered text: { "Message" : "Order 42 moved to ready." , "State" : { "Message" : "Order 42 moved to ready." , "OrderId" : 42 , "Status" : "ready" , "{OriginalFormat}" : "Order {OrderId} moved to {Status}." } } In .NET 10, the typical shape keeps one rendered message at the top level: { "Message" : "Order 42 moved to ready." , "State" : { "OrderId" : 42 , "Status" : "ready" , "{OriginalFormat}" : "Order {OrderId} moved to {Status}." } } Microsoft documents this as a behavioral breaking change and recommends that parsers use the top-level property. The official compatibility note also gives an essential caveat: State.Message may still appear when its content differs from the top-level value. I therefore do not reject a record merely because both properties exist. This is not a loss of structured logging data. OrderId , Status , and {OriginalFormat} remain useful fields inside State . The part that changed is where a consumer should get the rendered sentence. Prefer the top-level Message and keep State structured A legacy-only extractor is brittle because it assumes the duplicate is the contract: static string ? ReadLegacyOnly ( JsonElement root ) => root . TryGetProperty ( "State" , out var state ) && state . TryGetPro

2026-08-23 原文 →
AI 资讯

A Reason Code Without a Source Is Half a Diagnostic

A failure message can be technically correct and still be frustratingly incomplete. Consider a timeout. It tells us something important about the failure mechanism, but not which operation encountered it. Adding the complete request target might answer that question, yet it can also expose identifiers, query parameters, access material, or other data that never belonged in a broadly visible diagnostic record. A safer middle ground is to give failures two separate coordinates: a reason code that explains how the operation failed, and a bounded operation label that explains where it failed. That distinction makes diagnostics more useful without turning failure handling into an accidental data-exposure channel. A reason code is not a location Reason codes describe failure mechanics. Generic examples might include deadline , cancelled , unauthorised , or invalid_response . These codes are valuable because they let systems group similar outcomes. A dashboard can count deadline failures across operations, while application logic can decide whether a particular reason is retryable. What a reason code cannot reliably explain is the operation being attempted. A deadline during a summary read may require a different investigation from a deadline while assembling a detailed response. Combining both meanings into one free-form message makes failures harder to query and encourages presentation text to become an informal data model. Model the two coordinates separately A deliberately generic, invented C# model might look like this: public enum OperationArea { Summary , Detail , Archive } public sealed record FailureDetail ( string ReasonCode , OperationArea ? Area = null ); The reason remains suitable for classification. The operation label adds location without carrying an unrestricted request value. An enum is not the only option. A validated value object or centrally managed set of constants can work too. The important constraint is that labels come from a small, reviewed voca

2026-08-21 原文 →
AI 资讯

MCP C# SDK Hybrid Sessions: Serve Old and New Clients on One Endpoint

The MCP C# SDK hybrid sessions option solves an awkward upgrade boundary: some clients still use the 2025-11-25 initialize handshake and depend on sessions, while clients on 2026-07-28 expect every HTTP request to stand alone. I want both groups to reach one ASP.NET Core endpoint without making modern clients downgrade or stripping useful behavior from legacy clients. The stable C# SDK 2.2.0 release added exactly that path with HttpServerSessionMode.StatefulForInitializeClients . The release notes describe it as hybrid stateful/stateless serving, and the official session-mode guide spells out the per-request behavior. Why one global session switch fails The 2026-07-28 MCP revision removed the initialize handshake and Mcp-Session-Id from its wire format. Client identity, capabilities, and protocol version travel with each request instead. The final specification announcement explains why the core moved toward request/response statelessness. That creates a migration choice for an existing server. With HttpServerSessionMode.Stateful , initialize-era clients receive full sessions. A modern request is refused so a dual-path client can fall back to the older handshake. Compatibility is preserved, but the client does not use the new protocol natively. With HttpServerSessionMode.Stateless , every request is independent. That is the right default for servers that do not need session state, unsolicited notifications, resource subscriptions, or older server-to-client flows. It may be too abrupt when deployed clients still rely on those features. Hybrid mode makes the decision from the incoming request instead of applying one choice to the endpoint. Configure MCP C# SDK hybrid sessions The server configuration is deliberately small: builder . Services . AddMcpServer () . WithHttpTransport ( options => { options . SessionMode = HttpServerSessionMode . StatefulForInitializeClients ; }) . WithTools < DemoTools >(); app . MapMcp ( "/mcp" ); An initialize-era client sends an initial

2026-08-20 原文 →
AI 资讯

MCP x-mcp-header Validation: Keep Bad Tool Schemas Out of tools/list

MCP x-mcp-header validation is easy to miss because the annotation looks like ordinary JSON Schema metadata. On the 2026-07-28 Streamable HTTP transport, it is a wire contract: the client copies selected tool arguments into Mcp-Param-* headers, intermediaries can act on those headers, and the server checks them against the JSON-RPC body. I treat that contract as something to test before a tool reaches tools/list . A bad suffix, an unsupported type, or an unreachable annotation makes the whole tool definition invalid. Silently accepting it only moves the failure to a harder place to diagnose. Why the same value travels twice The final Streamable HTTP specification mirrors request metadata into HTTP headers so a load balancer, gateway, or WAF does not need to parse JSON-RPC. A server can add x-mcp-header to a tool property: { "type" : "object" , "properties" : { "region" : { "type" : "string" , "x-mcp-header" : "Region" } } } A call with "region": "us-west1" then carries: Mcp-Param-Region: us-west1 The official C# SDK can generate that schema from a parameter attribute: [ McpServerTool ] public static string ExecuteSql ( [ McpHeader ( "Region" )] string region , string query ) => $"Queued for { region } " ; Current C# SDK v2 tool documentation describes both schema generation and automatic header projection. The feature is on the stable v2 line; it is not necessary to pin an earlier preview or release candidate. MCP x-mcp-header validation rules The final tool definition rules are deliberately narrow. The annotation value must be a non-empty HTTP field-name token and must be unique without regard to case. Region and region therefore collide. Control characters, spaces, and separators such as a colon are not valid suffix characters. Only string , integer , and boolean properties can be mirrored. JSON Schema number is excluded, and integer values must stay between -(2^53 - 1) and 2^53 - 1 so every conforming implementation can represent the value exactly. Reachability i

2026-08-20 原文 →
AI 资讯

Implementing Feature Management in .NET: The Lazy Way

Microsoft did the hard work so you don't have to. The Microsoft.FeatureManagement library integrates directly with .NET's configuration and dependency injection systems, which means you can get feature flags working with minimal code and a solid foundation. For the full documentation, check out the Microsoft Feature Management documentation . Let's get this thing running. Installation Add the NuGet package to your project: dotnet add package Microsoft.FeatureManagement.AspNetCore That's it for dependencies. No magic rituals required. Configuration Register the feature management services in Program.cs : builder . Services . AddFeatureManagement (); By default, feature flags are read from the FeatureManagement section of your appsettings.json : { "FeatureManagement" : { "NewDashboard" : true , "ExperimentalSearch" : false } } Flag names are strings. Values are booleans. Simple. Checking a Flag in Code Inject IFeatureManager wherever you need to check a flag: public class DashboardController : Controller { private readonly IFeatureManager _featureManager ; public DashboardController ( IFeatureManager featureManager ) { _featureManager = featureManager ; } public async Task < IActionResult > Index () { if ( await _featureManager . IsEnabledAsync ( "NewDashboard" )) { return View ( "NewDashboard" ); } return View ( "OldDashboard" ); } } That's the whole pattern. Inject. Check. Branch. Repeat. Using Feature Filters Boolean flags are useful, but sometimes you need something a little more sophisticated. The library supports feature filters for things like: Percentage rollouts Time windows User targeting For example, you can enable a feature for a percentage of requests: { "FeatureManagement" : { "BetaFeature" : { "EnabledFor" : [ { "Name" : "Percentage" , "Parameters" : { "Value" : 20 } } ] } } } This enables BetaFeature for 20% of requests. The library handles the sampling. You handle the business logic. Everybody wins. Razor Tag Helpers Building a Razor-based UI? The lib

2026-08-19 原文 →
AI 资讯

xUnit 4 ParallelMode.All: Protect Shared State from Test Races

xUnit 4.0.0 makes full test-case parallelization an explicit option. That is useful, but xUnit 4 ParallelMode.All changes a quiet assumption in many suites: tests in the same class, including separate rows of one theory, may now overlap. A static fake, shared fixture, temporary file, or database record that was safe under collection-level parallelism can become a race. I treat this as an isolation change, not a speed switch. Before enabling it across a suite, I want a deterministic failure that proves the risk and a deterministic check for each guardrail. What xUnit 4 ParallelMode.All changes The xUnit.net v3 4.0.0 release notes describe full test-case parallelization as a new feature. The default is still ParallelMode.Collections , so upgrading does not silently enable the broader mode. I have to opt in at the assembly level: using Xunit.Sdk ; using Xunit.v3 ; [ assembly : Parallelization ( Mode = ParallelMode . All , MaxThreads = 2 , Algorithm = ParallelAlgorithm . Conservative )] With Collections , tests within a collection are serialized. With All , every test case is eligible to run beside every other test case. That includes two cases from the same class and two pre-enumerated rows from the same theory. The official parallel test execution guide documents the modes, algorithms, and available opt-out scopes. I set MaxThreads = 2 in the sample so the scheduling condition is easy to inspect. It is a demonstration setting, not a recommendation for CI. The right value depends on available CPU, memory, and the external systems touched by the tests. Before changing the mode, I scan for mutable static fields, IClassFixture and ICollectionFixture implementations, fixed file names, environment-variable changes, test servers bound to fixed ports, and records addressed by shared IDs. I also check theory data sources for objects that rows can mutate. That inventory tells me whether the resource should become concurrency-safe, receive a unique per-test identity, or stay beh

2026-08-18 原文 →
AI 资讯

ASP.NET Core 10 Authentication Metrics: Distinguish No Result from Failure

When every unauthorized request becomes the same dashboard line, diagnosis turns into guessing. ASP.NET Core 10 authentication metrics give me a better split: did the handler have nothing to authenticate, reject supplied credentials, or accept them? That distinction matters because a client deployment that drops credentials needs a different response from a surge of malformed or expired credentials. ASP.NET Core 10 added built-in authentication and authorization instruments to System.Diagnostics.Metrics . I can collect them without rewriting each handler, and I can lock their behavior into an offline test before wiring up a production exporter. Why one 401 hides two different problems A protected endpoint normally challenges an unauthenticated caller. The final status is 401 whether the caller sent nothing or the handler rejected what it received. The authentication duration histogram exposes the missing context through aspnetcore.authentication.result : Result What the handler reported A common interpretation none No authentication result No applicable credentials were available failure Authentication failed Supplied credentials were rejected or processing failed success A principal was created Authentication completed successfully _OTHER Another framework result Preserve it as an explicit catch-all none is a handler result, not a universal synonym for “missing Authorization header.” A policy scheme or custom handler can make a different choice. I verify the behavior of the schemes I actually deploy instead of building an alert from the label alone. Likewise, success means the handler produced an authentication ticket. Authorization can still deny that principal, so it does not promise a 2xx response. The separate aspnetcore.authentication.challenges counter answers another question: how often was a scheme challenged? Both a none result and a failure result can be followed by a challenge, so challenge count cannot replace the result split. A challenge is an authent

2026-08-18 原文 →
AI 资讯

MFA Enabled Is Not MFA Verified

A two-factor flag in the user store looks like a reassuring authorization check. It tells us the account has a second factor configured. For a sensitive operation, however, that is only half the question. The other half is about the session in front of us: did this cookie actually complete a second-factor challenge? Those facts can change independently. Treating them as interchangeable can silently promote an old password-only session after the account enables MFA. Two questions that look like one Account capability answers questions such as: Is a factor enrolled now? Could the account complete an MFA challenge? Has that capability since been disabled? Session assurance answers different questions: Which authentication steps produced this session? Did the framework issue this cookie after an MFA challenge? Is the evidence trusted, or merely a user-supplied claim? An enrolled account can still have a password-only session. A previously verified session can also outlive a later change to the account’s factor state. One signal cannot safely stand in for both. The transition that exposes the gap Snapshot tests often miss this because the final state looks correct. The account has MFA enabled, the user is authenticated, and a policy succeeds. Now test the transition instead: Sign in with a password and receive a normal application cookie. Enable MFA for the account without replacing that cookie. Use the original cookie against a sensitive operation. If authorization checks only the current enrolment flag, step three may succeed. Nothing about the original authentication ceremony changed, but the session has effectively been upgraded by a later database write. That is the important boundary: changing account capability must not rewrite the history of an already-issued session. Use two independent signals A generalized policy can be expressed like this: if (! session . IsAuthenticated || ! session . HasTrustedMfaEvidence ) return Deny ; if (! await accountStore . IsMfaStil

2026-08-17 原文 →
AI 资讯

Voice In. Words Out: The Free, 100% Offline Voice Typing App for Windows

Imagine this: You’re drafting a long email, writing a report, or responding to a wave of Slack messages. Instead of hunching over your keyboard and typing at 40 words per minute, you simply hold down Ctrl + Space , speak your thoughts at 150+ words per minute, and release the keys. Instantly, clean, perfectly punctuated, polished text appears right where your cursor is. Meet Vacanam — a free, 100% private, offline voice typing tool built for Windows 10 & 11. 😫 Why Most Voice Typing Tools Are Frustrating If you’ve ever tried built-in dictation tools or commercial transcription services, you’ve likely run into the same annoyances: They Send Your Voice to the Cloud : Many tools stream your microphone audio to remote servers. If you work with sensitive emails, client data, or private thoughts, that’s an immediate dealbreaker. They Require an Internet Connection : Try dictating on an airplane, during spotty Wi-Fi, or in a secure offline room — they simply refuse to work. Punctuation is a Headache : You have to awkwardly say things like "Hello comma how are you question mark" just to get a basic sentence right. Subscription Fatigue : Most good dictation apps charge $10 to $30 every single month. We built Vacanam (वचनम् — Sanskrit for Voice & Speech ) to fix all of this once and for all. 🌟 The Superpowers: What Makes Vacanam Different? 1. 🎙️ Works in Every Single Windows App Vacanam doesn’t trap you inside a special recording window. It works universally: Productivity & Docs : Microsoft Word, Google Docs, Notion, Obsidian, OneNote Communication : Slack, Microsoft Teams, WhatsApp Desktop, Discord, Outlook, Gmail Browsers & Editors : Chrome, Edge, Firefox, Notepad, VS Code, Terminals Just click into any text box, hold Ctrl + Space, speak, and let go. 2. 🪄 Automatic AI Polish (No More "Ums" or Missing Commas) When we talk, we hesitate, say "um" , repeat words, and forget punctuation. Vacanam features an optional Built-in AI Assistant that runs silently on your computer: Remov

2026-08-14 原文 →
AI 资讯

2026-08-12 - 1 - ProForma - Guards

Hello I'm Marlene and I invite you to follow my journey developing ProForma.net. But since this is my first post about ProForma, I will give you an overview of what I'm trying to achieve. What ProForma.net is planned to be The main Goal is to develop an application shell for schema based Applications. You will have mainly to different types of UI schemes, first for the Window Layout, there you will tell which elements are contained in the different application sections, like what Buttons or Menus you will have in the window title bar, or what sidebar tabs you will provide for the Ribbons, what the content area is filled with (spoiler I'm going to use flexlayout-react https://github.com/caplin/FlexLayout ). As host application I will write a C# application using the WebView2 abstraction library Photino ( https://www.tryphotino.io/ ). What you can expect In this dev diary series I'll show what I was working on, I'll show you some code and will explain why did to choose the way I did it, or will share some thoughts about the project or the architecture. I also will show you how to write plugins for ProForma, because I plan to handle everything as a plugin so you can change the most aspects of the app. The journey begins: overcome the guard Ok, most of you will know it... parameter checking on top of a method... nearly endless 'if throw' constructs... they are ugly... if (! Directory . Exists ( physicalPath )) throw new DirectoryNotFoundException ( $"Could not find the given path ' { physicalPath } '." ); if ( _directories . ContainsKey ( urlPrefix )) throw new Exception ( $"Key ' { urlPrefix } ' already exists." ); if ( _directories . ContainsValue ( physicalPath )) throw new Exception ( $"Physical Path ' { physicalPath } ' already exists." ); I mean who wants to read that? I don't. So I wanted guards, and I've could used some 3rd Party library, but instead I came up with my own solution for the Guards, since I don't always want to throw the exception on a failed asser

2026-08-12 原文 →
AI 资讯

Building an offline-first travel app in .NET MAUI (on-device OCR, currency & maps, no backend)

A build note from Horizon Software , a one-person Android studio. WanderWallet is a travel budget app, and the whole thing runs on the phone: no account, no backend, no cloud. Here's how the parts that look like they need a server actually work without one. The one constraint that shaped everything WanderWallet has a single non-negotiable rule: it has to work with no signal. You're three countries into a trip, your phone's in airplane mode to dodge roaming charges, and you still need to know whether you're on budget. That one requirement quietly makes most of the architectural decisions for you — no login, no server round-trips, and every feature that would normally lean on a cloud API has to earn its keep another way. The stack is deliberately boring: .NET MAUI (Android-first), CommunityToolkit.Mvvm , sqlite-net-pcl for storage, and SkiaSharp for anything I draw myself. Everything the app records lives in a local SQLite database on the device and nowhere else. "Backup" is a file you export and keep — there's no server to back up to . The three features people assume need a backend turned out to be the most interesting to build, precisely because they don't. 1. Currency conversion that survives airplane mode A travel budget app that can't convert currencies offline is useless at exactly the moment you need it. So rates aren't fetched on demand. Whenever the app happens to have a connection it refreshes exchange rates for ~155 currencies and caches the whole table locally . From then on every conversion is local arithmetic — a connection only ever buys you a fresher table, never the ability to convert. The design decision that took me longest to get right: capture the conversion immutably, at entry time. Each expense stores the original amount, its original currency, the converted home-currency amount, and the exact rate used — and that rate is never recalculated: public class Expense { public double Amount { get ; set ; } // in OriginalCurrency public string Origina

2026-08-08 原文 →
AI 资讯

The AI Has Hands

Ask Crysta — the AI agent on CrystaCode.ai — to switch the site to dark mode, and the site actually turns dark. Ask it to show the login popup, and the modal actually opens. The model doesn't just answer anymore; it operates the UI. But here's the thing: the LLM lives on the server , and the UI lives in the browser . A model can't click buttons. So how do you give a remote brain hands? The answer is a pattern we ended up calling the Client Driver Skill : function calling, with SignalR as the hand. The Problem The first version of our chat was a one-way street. The model could say "sure, I'll take you to the plans page" — and then nothing happened. The answer was text; the UI was deaf. You have two classic options: The client polls the server for commands (ugly, wasteful, feels like 2010) The server pushes commands to the client (real-time, instant, exactly what SignalR is for) We went with the push. The flow became: the model calls a function → the function runs on the server → the server pushes a typed command over SignalR → the client executes it. How It Works (The Full Loop) [Browser] [Server] | | | 1. "switch to dark mode" | | ---- InvokeAsync ---------> | | | 2. Brain runs, model sees | | the UpdateSiteTheme tool | | 3. Model calls the function | | (function calling) | | 4. Push to the exact tab: | <--- ChangeSiteTheme ------ | Clients.Client(connId) | 5. ThemeService flips it | | 6. "Site theme changed..." | 5b. return value re-enters | | the model's context | <--- chat answer ---------- | User types "switch to dark mode" → the Blazor client calls the hub The server session runs the brain; the model sees a tool called UpdateSiteTheme The model decides the user wants dark mode and calls the function The server pushes ChangeSiteTheme(DarkMode) to the exact browser connection The client applies the theme and re-renders The function's return value goes back into the model's context, so the AI knows the theme changed and confirms it in the chat 1) The Client Regist

2026-08-04 原文 →
AI 资讯

My Shell Scripts Speak C# Now

Every couple of weeks I need a twenty-line program. Find what's bloating a build agent's disk, dedupe a CSV, hash-check a folder. For fifteen years the honest answer to "which language?" was not C# — by the time I'd done mkdir , dotnet new console , and named yet another throwaway csproj, the moment had passed. So those little jobs went to bash or Python, and I grumbled quietly every time. .NET 10 removed the ritual. You write one .cs file and run it. I'd been meaning to check how well this actually holds up for real scripts, so this week I did — nothing fancy, one Linux container and a stopwatch. One file, no project Here's biggest.cs , a small utility that lists the largest files under a directory. The whole program is this one file — no csproj anywhere: # !/ usr / bin / env dotnet # : package Humanizer @ 3.0 . 10 using Humanizer ; var root = args . Length > 0 ? args [ 0 ] : "." ; var top = args . Length > 1 && int . TryParse ( args [ 1 ], out var n ) ? n : 10 ; var files = new DirectoryInfo ( root ) . EnumerateFiles ( "*" , new EnumerationOptions { RecurseSubdirectories = true , IgnoreInaccessible = true , AttributesToSkip = FileAttributes . ReparsePoint }) . OrderByDescending ( f => f . Length ) . Take ( top ) . ToList (); foreach ( var f in files ) { var size = f . Length . Bytes (). Humanize ( "#.#" ); var age = ( DateTime . UtcNow - f . LastWriteTimeUtc ). Humanize (); Console . WriteLine ( $" { size , 10 } { f . FullName } (modified { age } ago)" ); } Two lines are new. #:package Humanizer@3.0.10 is a NuGet reference written as a directive, right in the source. The shebang we'll get to in a minute. Everything else is the C# you already write, top-level statements and all. $ dotnet run biggest.cs -- ~/.dotnet 5 Top 5 files under /root/.dotnet: 37.6 MB .../FSharp.Compiler.Service.dll (modified 46 seconds ago) 18.7 MB .../Microsoft.CodeAnalysis.CSharp.dll (modified 46 seconds ago) 18.7 MB .../Roslyn/bincore/Microsoft.CodeAnalysis.CSharp.dll (modified 45 seconds

2026-08-01 原文 →
AI 资讯

C# Crash Course for Beginners

Hey everyone, I'm excited to share my brand-new C# Crash Course for Beginners on YouTube! 🎉 For those of you who are new here, I'm Amir, a software developer who enjoys learning new technologies and creating programming tutorials that are practical, beginner-friendly, and straight to the point. If you've been thinking about learning C#, this course is the perfect place to start. C# is one of the most popular programming languages in the world and is widely used for desktop applications, web development with ASP.NET, cloud services, game development with Unity, and enterprise software. Combined with the power of the .NET ecosystem, it provides an excellent foundation for building modern applications. In this one-hour crash course, we'll start from the very beginning by setting up the .NET development environment and learning the essential command-line tools. From there, we'll gradually build our understanding of the language through practical demonstrations and live coding examples. Throughout the course, you'll learn: How to install and configure the .NET SDK Using the .NET CLI and .NET Script Variables and data types String interpolation Arithmetic, comparison, and logical operators Conditional statements Loops Methods and functions Arrays and collections Lists, Dictionaries, and HashSets LINQ fundamentals Classes, objects, and Object-Oriented Programming (OOP) Records and modern C# features Pattern Matching You'll also get a preview of a real-world application that we'll build together in a future tutorial series, showing how these concepts come together in an actual project. This course focuses on building a strong understanding of C# fundamentals. Topics such as asynchronous programming with async and await are intentionally left for a dedicated tutorial, where we can explore them properly with practical examples. Whether you're completely new to programming or coming from another language like Java, Python, JavaScript, Go, or Rust, I hope this course helps make

2026-07-30 原文 →
AI 资讯

The one seam, shown: Inline up close

In post 10 I closed the composition-versus-coherence question with one paragraph: I kept strict object-scoping, reserved an Inline operator for later, and rejected automatic reach-down. That was true, and it was too fast. A reader told me the reserved operator was not clear from a sentence, which is fair. A design record that asserts a decision without showing it is not really a record. So here is the seam, worked out. First, the good news that made this only one seam and not ten: composition and coherence mostly do not collide. Composition substitutes complex types ; coherence binds scalar facets ; those are disjoint kinds of member. A collection gives one persona per element. Draw order falls out of the eager construction rule from post 7. And the resolver pipeline I pre-paid for back in post 4 turned out to be coherence's host. The two threads layer cleanly almost everywhere. Almost. The discontinuity Coherence is object-scoped (post 5). That one rule has a consequence that only shows up once you have composition encouraging you to split a type across nested objects: moving a facet into a child changes whether it coheres. // Flat: Email is a Person facet, so it coheres with the name. Customer { FirstName , LastName , Email } // -> "Maria", "Gonzalez", "maria.gonzalez@..." // Decomposed: Contact is its own scope. A lone Email there does not activate a // persona (one corroborating member, no name anchor), so it is a plain, unrelated email. Customer { FirstName , LastName , Contact : ContactInfo { Email } } // -> "Maria", "Gonzalez", "rwilson@..." Same three fields, same intent, different result, decided entirely by which object they live on. That is the discontinuity. The options A, strict: object-scoping stays. The decomposed email does not cohere. Maximally predictable. The gap is the already-deferred cross-entity work. B, reach-down: a child with no entity of its own is absorbed into the parent scope. The email coheres. But "absorbed or not" now depends on hidd

2026-07-30 原文 →
AI 资讯

Building a Parking Puzzle in Unity: A Systems Breakdown of the Park Match Mechanic

Parking and matching puzzles look almost insultingly simple from the outside. A few cars, a cramped lot, tap or drag to move them out. But if you've actually tried to build one that feels tight — no janky collision resolution, no ambiguous "why didn't that move register" moments, no lag once the board gets crowded — you know there's a real systems design problem hiding underneath what looks like a weekend project. I recently went through the architecture of a parking/matching hybrid template built in Unity and wanted to break down the core systems the way I'd want them explained if I were reskinning or extending one myself. This isn't a marketing post — it's a walkthrough of the actual mechanics: how vehicle movement and collision resolution work, how the match/clear pipeline is structured, how level data is separated from movement logic so hundreds of levels don't require touching code, and how monetization hooks slot into natural break points without polluting gameplay scripts. If you want to see the finished product this breakdown is loosely based on, there's a working template here: Park Match Unity Game Template . Everything below applies whether you're building something similar from scratch or extending an existing base. Why Parking Puzzles Are Harder Than They Look The core loop — tap or drag a vehicle, it exits along a valid path, the lot clears one piece at a time — is trivial to describe and genuinely fiddly to implement well. Four problems show up almost immediately once you move past a static mockup: Valid-move detection : how do you know, at any given moment, which vehicles can actually move given their orientation and the current board state? Path resolution : once a vehicle starts moving, how does it navigate around other vehicles and obstacles without clipping through them or getting stuck mid-animation? Match/clear logic : when does a vehicle actually "clear" the board — on reaching an exit, on matching color/type with another vehicle, or both — an

2026-07-29 原文 →
AI 资讯

RockPlayer: Building a Modern Music Player with Angular, ASP.NET Core, Redis, and YouTube

Hello everyone! After publishing my Machine Learning with ML.NET series, I decided to turn the recommendation model into a complete application. In this new series, we build RockPlayer, a rock music player that combines modern software architecture, ASP.NET Core, Angular 22, Redis, and YouTube integration. Each article focuses on a different part of the project: 🎵 1. Introducing RockPlayer An overview of the project, its goals, and the overall architecture. https://devfullstack.net/blog/introducing-rockplayer 🔌 2. Adapters: Isolating the YouTube Provider Using the Adapter pattern to decouple the application from the YouTube integration. https://devfullstack.net/blog/adapters-isolating-the-youtube-provider ⚡ 3. No Database: Caching Lookups with Redis Using Redis to cache search results instead of storing external data in a database. https://devfullstack.net/blog/no-database-caching-lookups-with-redis 🅰️ 4. Angular 22 in Practice Applying modern Angular 22 features to build the user interface. https://devfullstack.net/blog/angular-22-in-practice 🚀 5. Building the RockPlayer API Building the API that orchestrates the application. https://devfullstack.net/blog/building-the-rockplayer-api ▶️ 6. The YouTube Adapter: Finding and Playing the Song Implementing the YouTube integration to search for and play songs. https://devfullstack.net/blog/the-youtube-adapter-finding-and-playing-the-song 🎧 7. RockPlayer in Angular 22: Onboarding Setting up the Angular application and organizing the project structure. https://devfullstack.net/blog/rockplayer-in-angular-22-onboarding 🎸 8. RockPlayer: Putting It All Together Bringing all the components together into a complete application. https://devfullstack.net/blog/rockplayer-putting-it-all-together I hope this series is useful for developers interested in software architecture, .NET, and Angular. See you there!

2026-07-27 原文 →