diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000000000..8fc5229bca8a9 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,21 @@ +{ + "name": "C# (.NET)", + "image": "mcr.microsoft.com/devcontainers/dotnet:latest" + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [5000, 5001], + // "portsAttributes": { + // "5001": { + // "protocol": "https" + // } + // } + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "dotnet restore", + + // Configure tool-specific properties. + // "customizations": {}, +} diff --git a/docs/ai/azure-ai-for-dotnet-developers.md b/docs/ai/azure-ai-for-dotnet-developers.md index b99a041519c47..e19c28f8cff03 100644 --- a/docs/ai/azure-ai-for-dotnet-developers.md +++ b/docs/ai/azure-ai-for-dotnet-developers.md @@ -1,7 +1,7 @@ --- title: Develop .NET apps that use Azure AI services description: This article provides an organized list of resources about Azure AI scenarios for .NET developers, including documentation and code samples. -ms.date: 05/17/2024 +ms.date: 12/19/2024 ms.topic: overview ms.custom: devx-track-dotnet, devx-track-dotnet-ai --- diff --git a/docs/ai/conceptual/embeddings.md b/docs/ai/conceptual/embeddings.md index bfee6673f3284..d041255d56bb5 100644 --- a/docs/ai/conceptual/embeddings.md +++ b/docs/ai/conceptual/embeddings.md @@ -3,7 +3,7 @@ title: "How Embeddings Extend Your AI Model's Reach" description: "Learn how embeddings extend the limits and capabilities of AI models in .NET." author: catbutler ms.topic: concept-article #Don't change. -ms.date: 05/14/2024 +ms.date: 12/19/2024 #customer intent: As a .NET developer, I want to understand how embeddings extend LLM limits and capabilities in .NET so that I have more semantic context and better outcomes for my AI apps. @@ -34,7 +34,7 @@ Use embeddings to help a model understand the meaning and context of text, and t Use audio embeddings to process audio files or inputs in your app. -For example, [Speech service](/azure/ai-services/speech-service/) supports a range of audio embeddings, including [speech to text](/azure/ai-services/speech-service/speech-to-text) and [text to speech](/azure/ai-services/speech-service/text-to-speech). You can process audio in real-time or in batches. +For example, [Azure AI Speech](/azure/ai-services/speech-service/) supports a range of audio embeddings, including [speech to text](/azure/ai-services/speech-service/speech-to-text) and [text to speech](/azure/ai-services/speech-service/text-to-speech). You can process audio in real-time or in batches. ### Turn text into images or images into text diff --git a/docs/ai/conceptual/understanding-openai-functions.md b/docs/ai/conceptual/understanding-openai-functions.md index 0f125eac1a7ec..e5d90460e86f2 100644 --- a/docs/ai/conceptual/understanding-openai-functions.md +++ b/docs/ai/conceptual/understanding-openai-functions.md @@ -3,7 +3,7 @@ title: "Understanding OpenAI Function Calling" description: "Understand how function calling enables you to integrate external tools with your OpenAI application." author: haywoodsloan ms.topic: concept-article -ms.date: 05/14/2024 +ms.date: 12/19/2024 #customer intent: As a .NET developer, I want to understand OpenAI function calling so that I can integrate external tools with AI completions in my .NET project. diff --git a/docs/ai/conceptual/understanding-tokens.md b/docs/ai/conceptual/understanding-tokens.md index 910d5ccd93206..bd804c5971403 100644 --- a/docs/ai/conceptual/understanding-tokens.md +++ b/docs/ai/conceptual/understanding-tokens.md @@ -3,7 +3,7 @@ title: "Understanding tokens" description: "Understand how large language models (LLMs) use tokens to analyze semantic relationships and generate natural language outputs" author: haywoodsloan ms.topic: concept-article -ms.date: 05/14/2024 +ms.date: 12/19/2024 #customer intent: As a .NET developer, I want understand how large language models (LLMs) use tokens so I can add semantic analysis and text generation capabilities to my .NET projects. @@ -11,7 +11,7 @@ ms.date: 05/14/2024 # Understand tokens -Tokens are words, character sets, or combinations of words and punctuation that are used by large language models (LLMs) to decompose text into. Tokenization is the first step in training. The LLM analyzes the semantic relationships between tokens, such as how commonly they're used together or whether they're used in similar contexts. After training, the LLM uses those patterns and relationships to generate a sequence of output tokens based on the input sequence. +Tokens are words, character sets, or combinations of words and punctuation that are generated by large language models (LLMs) when they decompose text. Tokenization is the first step in training. The LLM analyzes the semantic relationships between tokens, such as how commonly they're used together or whether they're used in similar contexts. After training, the LLM uses those patterns and relationships to generate a sequence of output tokens based on the input sequence. ## Turning text into tokens @@ -89,11 +89,7 @@ Output generation is an iterative operation. The model appends the predicted tok ### Token limits -LLMs have limitations regarding the maximum number of tokens that can be used as input or generated as output. This limitation often causes the input and output tokens to be combined into a maximum context window. - -For example, GPT-4 supports up to 8,192 tokens of context. The combined size of the input and output tokens can't exceed 8,192. - -Taken together, a model's token limit and tokenization method determine the maximum length of text that can be provided as input or generated as output. +LLMs have limitations regarding the maximum number of tokens that can be used as input or generated as output. This limitation often causes the input and output tokens to be combined into a maximum context window. Taken together, a model's token limit and tokenization method determine the maximum length of text that can be provided as input or generated as output. For example, consider a model that has a maximum context window of 100 tokens. The model processes our example sentences as input text: diff --git a/docs/ai/get-started-app-chat-scaling-with-azure-container-apps.md b/docs/ai/get-started-app-chat-scaling-with-azure-container-apps.md index 02551582304ee..312fab8f0d17d 100644 --- a/docs/ai/get-started-app-chat-scaling-with-azure-container-apps.md +++ b/docs/ai/get-started-app-chat-scaling-with-azure-container-apps.md @@ -1,7 +1,7 @@ --- title: Scale Azure OpenAI for .NET chat sample using RAG description: Learn how to add load balancing to your application to extend the chat app beyond the Azure OpenAI token and model quota limits. -ms.date: 05/16/2024 +ms.date: 12/19/2024 ms.topic: get-started ms.custom: devx-track-dotnet, devx-track-dotnet-ai # CustomerIntent: As a .NET developer new to Azure OpenAI, I want to scale my Azure OpenAI capacity to avoid rate limit errors with Azure Container Apps. diff --git a/docs/ai/get-started-app-chat-template.md b/docs/ai/get-started-app-chat-template.md index c8c90816af53c..103d4979a36f5 100644 --- a/docs/ai/get-started-app-chat-template.md +++ b/docs/ai/get-started-app-chat-template.md @@ -1,7 +1,7 @@ --- title: Get started with the chat using your own data sample for .NET description: Get started with .NET and search across your own data using a chat app sample implemented using Azure OpenAI Service and Retrieval Augmented Generation (RAG) in Azure AI Search. Easily deploy with Azure Developer CLI. This article uses the Azure AI Reference Template sample. -ms.date: 05/16/2024 +ms.date: 12/19/2024 ms.topic: get-started ms.custom: devx-track-dotnet, devx-track-dotnet-ai # CustomerIntent: As a .NET developer new to Azure OpenAI, I want deploy and use sample code to interact with app infused with my own business data so that learn from the sample code. diff --git a/docs/ai/how-to/content-filtering.md b/docs/ai/how-to/content-filtering.md index 1a4b6e013646e..e3227c8a77b04 100644 --- a/docs/ai/how-to/content-filtering.md +++ b/docs/ai/how-to/content-filtering.md @@ -5,13 +5,13 @@ ms.custom: devx-track-dotnet, devx-track-dotnet-ai author: alexwolfmsft ms.author: alexwolf ms.topic: how-to -ms.date: 05/13/2024 +ms.date: 12/19/2024 #customer intent: As a .NET developer, I want to manage OpenAI Content Filtering in a .NET app --- -# Work with OpenAI content filtering in a .NET app +# Work with Azure OpenAI content filtering in a .NET app This article demonstrates how to handle content filtering concerns in a .NET app. Azure OpenAI Service includes a content filtering system that works alongside core models. This system works by running both the prompt and completion through an ensemble of classification models aimed at detecting and preventing the output of harmful content. The content filtering system detects and takes action on specific categories of potentially harmful content in both input prompts and output completions. Variations in API configurations and application design might affect completions and thus filtering behavior. @@ -27,7 +27,7 @@ The [Content Filtering](/azure/ai-services/openai/concepts/content-filter) docum To use the sample code in this article, you need to create and assign a content filter to your OpenAI model. -1. [Create and assign a content filter](/azure/ai-services/openai/how-to/content-filters) to your provisioned GPT-35 or GPT-4 model. +1. [Create and assign a content filter](/azure/ai-services/openai/how-to/content-filters) to your provisioned model. 1. Add the [`Azure.AI.OpenAI`](https://www.nuget.org/packages/Azure.AI.OpenAI) NuGet package to your project. @@ -35,25 +35,15 @@ To use the sample code in this article, you need to create and assign a content dotnet add package Azure.AI.OpenAI ``` -1. Create a simple chat completion flow in your .NET app using the `OpenAiClient`. Replace the `YOUR_OPENAI_ENDPOINT`, `YOUR_OPENAI_KEY`, and `YOUR_OPENAI_DEPLOYMENT` values with your own. +1. Create a simple chat completion flow in your .NET app using the `AzureOpenAiClient`. Replace the `YOUR_MODEL_ENDPOINT` and `YOUR_MODEL_DEPLOYMENT_NAME` values with your own. - :::code language="csharp" source="./snippets/content-filtering/program.cs" id="chatCompletionFlow"::: + :::code language="csharp" source="./snippets/content-filtering/program.cs" ::: -1. Print out the content filtering results for each category. +1. Replace the `YOUR_PROMPT` placeholder with your own message and run the app to experiment with content filtering results. If you enter a prompt the AI considers unsafe, Azure OpenAI returns a `400 Bad Request` code. The app prints a message in the console similar to the following: - :::code language="csharp" source="./snippets/content-filtering/program.cs" id="printContentFilteringResult"::: - -1. Replace the `YOUR_PROMPT` placeholder with your own message and run the app to experiment with content filtering results. The following output shows an example of a prompt that triggers a low severity content filtering result: - - ```output - I am sorry if I have done anything to upset you. - Is there anything I can do to assist you and make things better? - - Hate category is filtered: False with low severity. - SelfHarm category is filtered: False with safe severity. - Sexual category is filtered: False with safe severity. - Violence category is filtered: False with low severity. - ``` +```output +The response was filtered due to the prompt triggering Azure OpenAI's content management policy... +``` ## Related content diff --git a/docs/ai/how-to/snippets/content-filtering/AIContentFiltering.csproj b/docs/ai/how-to/snippets/content-filtering/AIContentFiltering.csproj index b5ff6e91f980e..bd79a4df023fa 100644 --- a/docs/ai/how-to/snippets/content-filtering/AIContentFiltering.csproj +++ b/docs/ai/how-to/snippets/content-filtering/AIContentFiltering.csproj @@ -8,8 +8,10 @@ - - + + + + diff --git a/docs/ai/how-to/snippets/content-filtering/Program.cs b/docs/ai/how-to/snippets/content-filtering/Program.cs index 514240c01e443..f6537d51e3969 100644 --- a/docs/ai/how-to/snippets/content-filtering/Program.cs +++ b/docs/ai/how-to/snippets/content-filtering/Program.cs @@ -1,38 +1,19 @@ -// -using Azure; -using Azure.AI.OpenAI; +using Azure.AI.OpenAI; +using Azure.Identity; +using Microsoft.Extensions.AI; -string endpoint = "YOUR_OPENAI_ENDPOINT"; -string key = "YOUR_OPENAI_KEY"; +IChatClient client = + new AzureOpenAIClient( + new Uri("YOUR_MODEL_ENDPOINT"), + new DefaultAzureCredential()).AsChatClient("YOUR_MODEL_DEPLOYMENT_NAME"); -OpenAIClient client = new(new Uri(endpoint), new AzureKeyCredential(key)); - -var chatCompletionsOptions = new ChatCompletionsOptions() +try { - DeploymentName = "YOUR_DEPLOYMENT_NAME", - Messages = - { - new ChatRequestSystemMessage("You are a helpful assistant."), - new ChatRequestUserMessage("YOUR_PROMPT") - } -}; - -Response response = client.GetChatCompletions(chatCompletionsOptions); -Console.WriteLine(response.Value.Choices[0].Message.Content); -Console.WriteLine(); -// + ChatCompletion completion = await client.CompleteAsync("YOUR_PROMPT"); -// -foreach (var promptFilterResult in response.Value.PromptFilterResults) + Console.WriteLine(completion.Message); +} +catch (Exception e) { - var results = promptFilterResult.ContentFilterResults; - Console.WriteLine(@$"Hate category is filtered: - {results.Hate.Filtered} with {results.Hate.Severity} severity."); - Console.WriteLine(@$"Self-harm category is filtered: - {results.SelfHarm.Filtered} with {results.SelfHarm.Severity} severity."); - Console.WriteLine(@$"Sexual category is filtered: - {results.Sexual.Filtered} with {results.Sexual.Severity} severity."); - Console.WriteLine(@$"Violence category is filtered: - {results.Violence.Filtered} with {results.Violence.Severity} severity."); + Console.WriteLine(e.Message); } -// \ No newline at end of file diff --git a/docs/ai/index.yml b/docs/ai/index.yml index af00f5e0e6880..dc4e838909e12 100644 --- a/docs/ai/index.yml +++ b/docs/ai/index.yml @@ -8,7 +8,7 @@ metadata: description: Samples, tutorials, and education for using AI with .NET ms.topic: hub-page ms.service: dotnet - ms.date: 05/13/2024 + ms.date: 12/19/2024 author: alexwolfmsft ms.author: alexwolf diff --git a/docs/ai/quickstarts/quickstart-local-ai.md b/docs/ai/quickstarts/quickstart-local-ai.md index 41abf32ca9c0c..d8abcfe1562fe 100644 --- a/docs/ai/quickstarts/quickstart-local-ai.md +++ b/docs/ai/quickstarts/quickstart-local-ai.md @@ -1,7 +1,7 @@ --- title: Quickstart - Connect to and chat with a local AI using .NET description: Set up a local AI model and chat with it using a .NET console app and the Microsoft.Extensions.AI libraries -ms.date: 11/24/2024 +ms.date: 12/19/2024 ms.topic: quickstart ms.custom: devx-track-dotnet, devx-track-dotnet-ai author: alexwolfmsft diff --git a/docs/csharp/language-reference/operators/deconstruction.md b/docs/csharp/language-reference/operators/deconstruction.md new file mode 100644 index 0000000000000..de77c4f3c86aa --- /dev/null +++ b/docs/csharp/language-reference/operators/deconstruction.md @@ -0,0 +1,61 @@ +--- +title: "Deconstruction expression - extract properties or fields from a tuple or other type" +description: "Learn about deconstruction expressions: expressions that extract individual properties or fields from a tuple or user defined type into discrete expressions." +ms.date: 12/17/2024 +--- +# Deconstruction expression - Extract properties of fields from a tuple or other user-defined type + +A *deconstruction expression* extracts data fields from an instance of an object. Each discrete data element is written to a distinct variable, as shown in the following example: + +:::code language="csharp" source="./snippets/shared/Deconstruction.cs" id="TupleDeconstruction"::: + +The preceding code snippet creates a [tuple](../builtin-types/value-tuples.md) that has two integer values, `X` and `Y`. The second statement *deconstructs* that tuple and stores the tuple elements in discrete variables `x` and `y`. + +## Tuple deconstruction + +All [tuple types](../builtin-types/value-tuples.md) support deconstruction expressions. Tuple deconstruction extracts all the tuple's elements. If you only want some of the tuple elements, use a [discard](../tokens/discard.md) for the unused tuple members, as shown in the following example: + +:::code language="csharp" source="./snippets/shared/Deconstruction.cs" id="TupleDeconstructionWithDiscard"::: + +In the preceding example, the `Y` and `label` members are discarded. You can specify multiple discards in the same deconstruction expression. You can use discards for all the members of the tuple. The following example is legal, although not useful: + +:::code language="csharp" source="./snippets/shared/Deconstruction.cs" id="AllDiscards"::: + +## Record deconstruction + +[Record](../builtin-types/record.md) types that have a [primary constructor](../builtin-types/record.md#positional-syntax-for-property-definition) support deconstruction for positional parameters. The compiler synthesizes a `Deconstruct` method that extracts the properties synthesized from positional parameters in the primary constructor. The compiler-synthesized `Deconstruction` method doesn't extract properties declared as properties in the record type. + +The `record` shown in the following code declares two positional properties, `SquareFeet` and `Address`, along with another property, `RealtorNotes`: + +:::code language="csharp" source="./snippets/shared/Deconstruction.cs" id="RecordDeconstruction"::: + +When you deconstruct a `House` object, all positional properties, and only positional properties, are deconstructed, as shown in the following example: + +:::code language="csharp" source="./snippets/shared/Deconstruction.cs" id="RecordDeconstructionUsage"::: + +You can make use of this behavior to specify which properties of your record types are part of the compiler-synthesized `Deconstruct` method. + +## Declare `Deconstruct` methods + +You can add deconstruction support to any class, struct, or interface you declare. You declare one or `Deconstruct` methods in your type, or as extension methods on that type. A deconstruction expression calls a method `void Deconstruct(out var p1, ..., out var pn)`. The `Deconstruct` method can be either an instance method or an extension method. The type of each parameter in the `Deconstruct` method must match the type of the corresponding argument in the deconstruction expression. The deconstruction expression assigns the value of each argument to the value of the corresponding `out` parameter in the `Deconstruct` method. If multiple `Deconstruct` methods match the deconstruction expression, the compiler reports an error for the ambiguity. + +The following code declares a `Point3D` struct that has two `Deconstruct` methods: + +:::code language="csharp" source="./snippets/shared/Deconstruction.cs" id="StructDeconstruction"::: + +The first method supports deconstruction expressions that extract all three axis values: `X`, `Y`, and `Z`. The second method supports deconstructing only the planar values: `X` and `Y`. The first method has an *arity* of 3; the second has an arity of 2. + +The preceding section described the compiler-synthesized `Deconstruct` method for `record` types with a primary constructor. You can declare more `Deconstruct` methods in record types. These methods can either add other properties, remove some of the default properties, or both. You can also declare a `Deconstruct` that matches the compiler-synthesized signature. If you declare such a `Deconstruct` method, the compiler doesn't synthesize one. + +Multiple `Deconstruct` methods are allowed as long as the compiler can determine one unique `Deconstruct` method for a deconstruction expression. Typically, multiple `Deconstruct` methods for the same type have different numbers of parameters. You can also create multiple `Deconstruct` methods that differ by parameter types. However, in many cases, too many `Deconstruct` methods can lead to ambiguity errors and misleading results. + +## C# language specification + +For more information, see the deconstruction section of the [C# Standard](~/_csharpstandard/standard/expressions.md#127-deconstruction). + +## See also + +- [C# operators and expressions](index.md) +- [Tuple types](../builtin-types/value-tuples.md) +- [Records](../builtin-types/record.md) +- [Structure types](../builtin-types/struct.md) diff --git a/docs/csharp/language-reference/operators/snippets/shared/Deconstruction.cs b/docs/csharp/language-reference/operators/snippets/shared/Deconstruction.cs new file mode 100644 index 0000000000000..581cadebe3d5f --- /dev/null +++ b/docs/csharp/language-reference/operators/snippets/shared/Deconstruction.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection.Emit; +using System.Text; +using System.Threading.Tasks; + +namespace operators; +public class Deconstruction +{ + public static void Examples() + { + // + var tuple = (X: 1, Y: 2); + var (x, y) = tuple; + + Console.WriteLine(x); // output: 1 + Console.WriteLine(y); // output: 2 + // + + // + var tuple2 = (X: 0, Y: 1, Label: "The origin"); + var (x2, _, _) = tuple2; + // + + // + var (_, _, _) = tuple2; + // + + // + var house = new House(1000, "123 Coder St.") + { + RealtorNotes = """ + This is a great starter home, with a separate room that's a great home office setup. + """ + }; + + var (squareFeet, address) = house; + Console.WriteLine(squareFeet); // output: 1000 + Console.WriteLine(address); // output: 123 Coder St. + Console.WriteLine(house.RealtorNotes); + // + + // + var point = new Point3D { X = 1, Y = 2, Z = 3 }; + + // Deconstruct 3D coords + var (x3, y3, z3) = point; + Console.WriteLine(x3); // output: 1 + Console.WriteLine(y3); // output: 2 + Console.WriteLine(z3); // output: 3 + + // Deconstruct 2D coords + var (x4, y4) = point; + Console.WriteLine(x4); // output: 1 + Console.WriteLine(y4); // output: 2 + // + } +} + +// +public record House(int SquareFeet, string Address) +{ + public required string RealtorNotes { get; set; } +} +// + +// +public struct Point3D +{ + public int X { get; set; } + public int Y { get; set; } + public int Z { get; set; } + + public void Deconstruct(out int x, out int y, out int z) + { + x = X; + y = Y; + z = Z; + } + + public void Deconstruct(out int x, out int y) + { + x = X; + y = Y; + } +} +// diff --git a/docs/csharp/language-reference/operators/snippets/shared/Program.cs b/docs/csharp/language-reference/operators/snippets/shared/Program.cs index 504183d61649a..5a5a7383d66c4 100644 --- a/docs/csharp/language-reference/operators/snippets/shared/Program.cs +++ b/docs/csharp/language-reference/operators/snippets/shared/Program.cs @@ -109,6 +109,9 @@ IsOperator.Examples(); Console.WriteLine(); +Console.WriteLine("============= deconstruction examples =========="); +Deconstruction.Examples(); + Console.WriteLine("============ Collection expressions ================="); CollectionExpressionExamples.Examples(); Console.WriteLine(); diff --git a/docs/csharp/language-reference/toc.yml b/docs/csharp/language-reference/toc.yml index 5de77035ebd85..1e4ef81c0dec8 100644 --- a/docs/csharp/language-reference/toc.yml +++ b/docs/csharp/language-reference/toc.yml @@ -322,6 +322,9 @@ items: - name: with expression href: ./operators/with-expression.md displayName: "records, copy" + - name: Deconstruction expression + href: ./operators/deconstruction.md + displayName: deconstruct, deconstruction - name: Operator overloading href: ./operators/operator-overloading.md - name: Statements @@ -364,11 +367,15 @@ items: - name: Comments displayName: /* */, // href: ./tokens/comments.md + - name: Discard + displayName: discard, discard pattern + href: ./tokens/discard.md - name: $ -- string interpolation href: ./tokens/interpolated.md - name: "@ -- verbatim identifier" href: ./tokens/verbatim.md - name: "\"\"\" -- raw string literal" + displayName: raw string, triple quote href: ./tokens/raw-string.md - name: Attributes read by the compiler items: diff --git a/docs/csharp/language-reference/tokens/discard.md b/docs/csharp/language-reference/tokens/discard.md new file mode 100644 index 0000000000000..0755f2811771d --- /dev/null +++ b/docs/csharp/language-reference/tokens/discard.md @@ -0,0 +1,26 @@ +--- +description: "A `_` is a discard, a placeholder for an unused variable in an expression" +title: "Discard - _" +ms.date: 12/17/2024 +--- +# Discard - A `_` acts as a placeholder for a variable + +The `_` character serves as a *discard*, which is a placeholder for an unused variable. + +There are two uses for the *discard* token: + +1. To declare an unused variable. A discard can't be read or accessed. + - Unused `out` arguments: `var r = M(out int _, out var _, out _);` + - Unused lambda expression parameters: `Action _ => WriteMessage();` + - Unused deconstruction arguments: `(int _, var answer) = M();` +1. To match any expression in a [discard pattern](../operators/patterns.md#discard-pattern). You can add a `_` pattern to satisfy exhaustiveness requirements. + +The `_` token is a valid identifier in C#. The `_` token is interpreted as a discard only when no valid identifier named `_` is found in scope. + +A discard can't be read as a variable. The compiler reports an error if your code reads a discard. The compiler can avoid allocating the storage for a discard in some situations where that is safe. + +## See also + +- [Tuples](../builtin-types/value-tuples.md) +- [Deconstruction](../tokens/discard.md) +- [Discard pattern](../operators/patterns.md#discard-pattern) diff --git a/docs/csharp/language-reference/tokens/index.md b/docs/csharp/language-reference/tokens/index.md index f85dc15a5abc4..5592c29f8419d 100644 --- a/docs/csharp/language-reference/tokens/index.md +++ b/docs/csharp/language-reference/tokens/index.md @@ -16,9 +16,11 @@ ms.assetid: 4c5c0539-2e37-40b7-91ce-75af5aabd3f9 # C# Special Characters -Special characters are predefined, contextual characters that modify the program element (a literal string, an identifier, or an attribute name) to which they are prepended. C# supports the following special characters: +Special characters are predefined, contextual characters that modify the program element (a literal string, an identifier, or an attribute name) to which they're prepended. C# supports the following special characters: - [@](./verbatim.md), the verbatim identifier character. - [$](./interpolated.md), the interpolated string character. +- ["""](./raw-string.md), A sequence of three or more `"` characters provides the delimiters for a raw string literal. +- [_](./discard.md), a `_` character represents a *discard*, which is a placeholder for an unused variable. -This section only includes those tokens that are not operators. See the [operators](../operators/index.md) section for all operators. +This section only includes those tokens that aren't operators. See the [operators](../operators/index.md) section for all operators.