Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
150806 stories
·
33 followers

C# 14 New Feature: Implicit Span Conversions

1 Share

In this talk, Ian Griffiths dives into the new implicit conversions introduced in C# 14, designed to make span types more natural to use.

He discusses how this change enhances performance, simplifies method signatures, and enables more powerful extension methods. However, Ian also warns about potential compatibility issues with older libraries and provides advice for library authors. He concludes with technical examples and solutions to common problems caused by these new features. Essential viewing for C# developers looking to leverage spans in their code more effectively.

  • 00:00 Introduction to Implicit Conversions in C# 14
  • 01:17 Understanding Span Types and Their Benefits
  • 02:35 Practical Examples of Using Spans
  • 05:04 Limitations and Issues in C# 13
  • 08:05 Improvements in C# 14
  • 12:42 New Implicit Span Conversions
  • 19:16 Potential Issues with Older Libraries
  • 23:33 Conclusion and Final Thoughts

C# 14 defines some new implicit conversions that make the use of span types feel more natural. In fact, the feature proposal's original title was "First Class Span Types." So this new feature is a change that should mostly go unnoticed. Things should just work the way you would expect. However, there are a couple of good reasons to know about the details.

One is that if you are using class libraries that were designed before C# 14, this change might cause problems. There are some circumstances in which code that used to compile just fine needs modification in C# 14 because you're using a library that made some assumptions that are no longer true. The resulting errors can be baffling if you don't understand the language changes that cause them.

Another good reason to understand these changes in detail is if you're a library author—this new feature might change certain design decisions. And there's another reason, which is more indirect, but was actually the first thing that caused a problem in my work: when library designers changed their libraries to adapt to this new feature, those library changes can cause errors when you recompile your code. But we'll get to that.

Just in case you've not come across .NET span types, they were introduced back in 2017 with C# version 7 and .NET Core 2.1, with two main goals. One was to make it much easier to write high-performance code. These types are what enable high-performance libraries like System.Text.Json to be so much faster and more efficient than older libraries. At Endjin, we wrote the AIS.NET libraries using spans and it delivered performance several orders of magnitude better than other libraries available at the time.

The other thing that spans do is make it easier to deal with all the different places data might live. An ordinary array object has to live on .NET's garbage-collected heap, but .NET provides ways to create arrays that live inside the call stack. And if we're interacting with operating system APIs or external libraries, we might want to use data that lives in regions of memory that are outside of .NET's control.

A span can handle all three scenarios. This means that we can write a single method that can work with data wherever it happens to be. Whether the data is on the .NET heap, the stack, or some other block of memory, a single method that takes a span can deal with all of these.

Here's an example. To keep it simple, this reports just the length of the data. We could do more interesting things, but this new language feature mainly affects how methods are invoked, so it's the method signature that matters here.

This line makes a span for some data that lives on the stack. It builds an array without creating work for the garbage collector. And as this shows, I can also get a span for a string.

A crucial feature of spans is that they don't copy data. They refer to the data wherever it may happen to sit. So this span does actually refer to the garbage-collected heap because I started with a string, and .NET string objects always live on the heap. But this line here shows one of the efficiency features of a span.

I can slice it. Here I said I want a span that refers to just the first five characters of the string. So conceptually, this is just like calling Substring, but the big advantage a span offers here is that it doesn't create a copy. Whereas the string type's Substring method creates a whole new string object on the heap containing a copy of the part you wanted, this refers to exactly the same string data as the previous span. It just remembers that we're only looking at the first five characters.

And this shows that if I've got an ordinary array on the heap, I can turn that into a span too. Again, this doesn't copy it; it just lets me refer to the data that's in an ordinary array using a span.

This flexibility means that a single method that takes a span will work for strings, arrays, or data that lives outside of the heap, and it will support efficient slicing of data. If I had written this method to take an array, it wouldn't work on strings or on stack-based data. And if I wanted to slice the input up, I'd need to allocate a new smaller array with a copy of the data. So it often makes more sense to define APIs that take spans instead of ones that take arrays or strings.

It's also quite common to define extension methods for spans. So I could, if I wanted, implement this functionality as an extension method instead. As you can see here—now, you may have deduced from the file names that this is all C# 13. So if I can do all this in C# 13, why did C# 14 need to change anything?

So now it's time to look at some things that don't work in C# 13. When I passed an array to this first method, I first assigned it into a variable of type ReadOnlySpan of int, which shows us that an implicit conversion is available. So you might think I can just pass the array directly to the method, but I got an error.

It turns out that the compiler can't infer the right type argument. If I provide the type argument, then it works. So it's possible, but a bit inconvenient. And if I try to pass a string directly, it's the same story. A suitable implicit conversion exists, but I need to provide the type argument explicitly for the compiler to understand what I want.

What about the extension method? When I tried that, it doesn't work. The compiler error tells us that it's looking for a method accepting a first argument of type int array. In fact, the method will accept that if I call it explicitly, instead of as an extension method.

Again, I need to provide the type argument. So maybe that was the problem with the extension method, but if I supply the type argument while trying to use this as an extension method, it still doesn't work. So it has actually found the extension method, but it's telling me that for this code to work, the extension method requires a receiver of type array.

And this is true. The rules in C# for when a method can be used as an extension method are slightly more restrictive than the rules for normal method invocation. When you use normal method invocation, then if the argument types don't match, the compiler will look for user-defined implicit conversions, and that's what's happening here.

The span type defines a custom implicit conversion from arrays, and the compiler has used that to make this method call work. And if I open up the compiled code in ILSpy, and if I find the main method—that call to ShowLengthExt is the last thing I did—and if I scroll down to the end, you can see that it is indeed invoking the span-defined custom implicit conversion operator.

So why doesn't it do the same thing when I try to invoke the same method with the extension method syntax? Well, I said the rules for extension methods are more restrictive, and one of the differences is that C# won't look for user-defined implicit conversion operators to try to make an extension method invocation work.

So how does C# 14 change all of this? I'll keep the C# 13 version open on the left so you can see how things change in C# 14. Let's start with the first method, so not the extension method. If I'm using a stack-allocated array, I now don't need to assign that into a span variable first. I can just pass it directly.

This code only works on C# 14. Although in practice it's not a big win because actually there's a simpler way to achieve this. Visual Studio suggests that I use the collection expression syntax, which is a more compact way to do exactly the same thing as this code. And if I were to accept that change, the result would actually work in C# 13 as well.

So although the new language feature happens to enable this example, it's only interesting insomuch as it helps us understand what's changed. Now, although C# 14 does define a new implicit conversion from string to span, it doesn't actually affect the particular example I wrote here. I'll show the impact later, but it doesn't change this bit here.

But there is a useful improvement with arrays. In the C# 14 example, I've removed the span variable because now when I pass an array to a generic method that takes a span, I no longer need to specify the type argument to make it work.

But the main goal with this language change is actually to make extension methods work better. So now I can use the extension method directly on the array, and that's the most important thing that these language changes enable.

So what's actually changed? Well, what they didn't do was just allow extension method resolution to apply custom implicit conversions to the receiver. That might seem like it would've been the obvious thing to do if your starting point is this code—I've declared read-only local variables to make use of the custom implicit conversions that ReadOnlySpan defines.

So it's tempting to say that I want the compiler to just do that work for me and use these same conversions automatically for extension methods. But this would be a quite broad change with the potential to change the meaning of a lot of existing code. If the compiler suddenly starts considering using implicit conversions in cases where it never used to, previously correct code might become ambiguous, causing compiler errors.

It may well be possible to create rules that would enable this to work while avoiding such problems. But the C# team's goal here wasn't to enable a whole new way of using extension methods. Their goal was much more focused. They wanted to make it easier to use spans. Again, the feature proposal's original title was "First Class Span Support."

So although the actual mechanisms are some changes to the type conversion rules, the intention is absolutely focused on spans. This feels like a slightly unusual move to me. Normally, C# has tended to prefer generality. For example, the motivation for LINQ was better language-level support for working with databases, but the set of language features that enabled LINQ are all far more general in nature.

So it surprised me a little to see the C# designers choose a narrower mechanism when a more general one might have solved the same problem. Now having read through all the public discussions that I could find about this feature, it looks to me like the C# team wanted the language to embody the quite specific relationship between arrays and spans, and that they concluded that general-purpose features would either be unable to capture the nuances or would be excessively complex.

And I think that's why they didn't simply allow implicit conversions to apply to extension method receivers. That would be a very general mechanism, but it provides nowhere to build in any special understanding of how spans and arrays relate. That said, I still don't have a clear idea of any specific scenarios that would've turned out worse if they'd done the more general thing.

In the public discussions I found where people asked if they couldn't just make user-defined implicit conversions available for extension method receivers, the C# team's responses stated the design philosophy without giving specific examples of why it was better. But anyway, to make this work, they've added a completely new kind of built-in implicit conversion to the language specification.

In C# 14, we now have something called an implicit span conversion. The rules for implicit span conversions directly reflect the relationship between arrays and spans. So specifically we have conversions to Span of T for any array of type T. We also have conversion to ReadOnlySpan of T, but this goes further. This supports covariant conversion.

So for example, if you've got an array of strings, not only is that convertible to a ReadOnlySpan of string, it's also convertible to a ReadOnlySpan of object because there's an implicit reference conversion from string to object. Also, since string implements IComparable, this new language feature also makes an implicit conversion from array of string to ReadOnlySpan of IComparable, and likewise for any other interface that string implements.

This new language feature also defines covariant conversion from Span to ReadOnlySpan. These covariant conversions are examples of how this language feature embeds the similarities between spans and arrays.

The final new implicit conversion is from string to ReadOnlySpan of char. Now, if you're familiar with spans, you might be perplexed at this stage because the runtime library span types already define implicit conversion operators for some of these conversions.

And it's true that some of the cases where these new conversions apply used to work in C# 13 without any special compiler handling. The runtime library defines the Span and ReadOnlySpan types with various user-defined implicit conversions, and the compiler applies these in exactly the same way as it handles custom implicit conversions for other types.

So what then is the point of these language changes? The key is that these implicit conversions are now a different kind from ordinary custom implicit conversions, meaning the language can define special rules for them. For example, this new feature adds some new type inference rules specifically for the case where an array is supplied where either a Span or ReadOnlySpan is expected.

This is why in C# 14 we no longer need to provide a type argument when passing an array to a method that accepts a span. In C# 13, the only way I could pass an array expression to my span-based method was to supply the type argument. The type inference rules aren't able to determine that a user-defined conversion could work here.

But in C# 14, there's an explicit inference rule for when an array is passed where a span is expected. So when I do that here, it correctly infers that the type argument should be the array element type, and it can then apply the built-in conversion from array to ReadOnlySpan.

This new language feature also changes the rules for extension methods. These new implicit span conversions are now in the list of conversions that the compiler will consider when trying to decide whether an extension method is applicable. That's why I can call my span-based extension method directly on the array in C# 14. The new rules mean that when trying to resolve this method, the compiler will consider the new implicit conversions from arrays to spans, which means that my ShowLengthExt method is now a candidate, which it wasn't in C# 13.

So we have these new built-in implicit conversions and some changes to the rules for extension method resolution. This enables the most important feature: extension methods for spans now work like you'd hoped they would with arrays.

But there's more. As well as affecting method resolution, the changes also open up some new scenarios around user-defined conversions. As you may know, C# won't chain together an unlimited number of user-defined implicit conversions. If you define a type called Stringable with an implicit conversion to string, the compiler will let you assign a Stringable into a variable of type string, or pass it as an argument to a method accepting a string.

Now, as I already mentioned, it won't use this implicit conversion for the implicit receiver argument of an extension method, but user-defined implicit conversions are available for ordinary arguments. But if we then define another type that has an implicit conversion to Stringable, which I suppose we would call Stringableable, then although we can assign a Stringableable directly into a variable of type Stringable, and we can assign a Stringable into a variable of type string, we can't assign a Stringableable directly into a string.

The compiler won't string the conversions together, so to speak. Essentially you get to use just one user-defined conversion within an expression.

Now, before C# 14, the implicit conversions available for spans were implemented as user-defined conversions in the usual way by the span types, and these effectively used up your quota.

Let me show you what I mean. This class defines a custom conversion from ReadOnlySpan of object. Here I've defined an object array. Now in C# 13, I can assign that into a ReadOnlySpan of object because ReadOnlySpan defines a user-defined implicit conversion operator, and then I can assign that span into a variable of type WithImplicit because of this conversion operator.

So we've effectively assigned an array of objects into a WithImplicit variable by going through a couple of implicit conversion operators. But if I try to go there directly, it doesn't work. The compiler won't discover the chain of custom conversion operators required to get there. Each assignment effectively has to use just one implicit conversion at a time.

But in C# 14, I can go straight from the array to my type, and that's because the conversion to span is now a built-in implicit conversion, meaning that I'm only attempting a single user-defined conversion in this expression.

Earlier I mentioned that this change can cause problems when you use old libraries written before this feature was added. This can happen when the new built-in implicit conversions mean that method overloads that were not previously applicable now become candidates. Code that used to compile without problems can end up being ambiguous.

For example, xUnit.NET had a potential problem. Now, in fact, they fixed this before .NET 10 shipped. But if you're using older versions of their libraries, you could still encounter this.

So the problem was that these two overloads existed of the Assert.Equals method. Now suppose you wrote a test containing this code. In C# 13, this would use the first overload. But now in C# 14, the built-in implicit conversion from array to span means that these two methods are considered equally good candidates. And so this is ambiguous.

As I said, they have already fixed this, but if you're a library author, how would you fix these kinds of problems in your own code? Well, if you've got a situation like the one just described, you've got a couple of options. You could define a new overload that the C# compiler will consider to be a better match than either of the existing ambiguous options.

For example, in the xUnit example I just described, one way to resolve the problem would be to add an overload that accepts a ReadOnlySpan as its first argument and an array as its second. This is a perfect match for the code I showed, so the compiler no longer considers using the other two options because those are both less direct.

Another possibility is to use the OverloadResolutionPriority attribute. You can put this on a method to break ties in cases that would normally be ambiguous.

Now there's one more way in which this new language feature could cause you problems. And ironically, it occurs when a library has been modified specifically to provide better support for spans. Now, we at Endjin actually ran into this in the Reactive Extensions for .NET, an open-source project that Endjin maintains.

The .NET runtime libraries define two extension methods, both called Reverse. Now they're designed for different scenarios. One is a standard LINQ operator, and LINQ to Objects defines this for any type that implements IEnumerable of T. This is a non-destructive method. It doesn't change its target. Instead, it returns a brand new object which provides all the values from the source object but in reverse order. So it's just a view over the underlying source.

But the MemoryExtensions class also defines a method called Reverse as an extension method for Span of T. And this is an in-place operation that modifies its target.

Now a couple of unit tests in the Reactive Extensions code base included expressions like this. Now, our intention here was to get an IEnumerable of char that enumerates the characters in a string in reverse order. And before C# 14, this resolved unambiguously to the IEnumerable of T extension.

But remember, one of the main goals of this new language feature is to enable extension methods to work on expressions whose type is not a span but which can be implicitly converted to a span. So in this case, that makes the span flavour of Reverse a viable candidate.

Now, this is a somewhat unusual example because these aren't really overloads of the same thing. They're two completely different methods that happen to have the same name. So despite what duck-typing advocates would have you believe, names alone do not always fully identify methods.

Now it's easy to fix. We just insert a call to AsEnumerable to force the array into an IEnumerable of char and now the overload we want is an exact match, so the error goes away. This particular form of problem is likely to be fairly unusual because it's just not all that common to get this kind of name collision.

So there it is. Whereas we used to have to rely on the custom implicit conversion operators that the span types defined, the language now defines built-in implicit conversions. These are designed specifically to express the relationship between spans and arrays, providing more precisely targeted support than would otherwise be possible. And this also enables span-based extension methods to be more widely applicable.

My name's Ian Griffiths. Thanks for listening.

Read the whole story
alvinashcraft
46 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

How to Create and Retrieve Secrets from Azure Key Vault using .NET Client Library?

1 Share
How to Create and Retrieve Secrets from Azure Key Vault using .NET Client Library? Create and retrieve secrets using .NET Client Library along with creation of Azure key vault resource using Azure CLI and assignment of role to Microsoft Entra (Azure AD) user name. A complete process flow to create and retrieve secrets from Azure Key Vault using .NET Client Library. A role is assigned to Microsoft Entra (Azure AD) user name. Signed in to Azure and executed the application. Resources are cleaned up.
Read the whole story
alvinashcraft
53 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

How to Work with YAML in Python – A Guide with Examples

1 Share

If you've ever worked with configuration files, Docker Compose, Kubernetes, or CI/CD pipelines, you've probably used YAML. It's everywhere in modern development, and for good reason: it’s human-readable, simple, and powerful.

In this guide, you'll learn how to work with YAML files in Python. We'll cover reading, writing, and manipulating YAML data in practice.

🔗 You can find the code on GitHub.

Prerequisites

Before working with YAML in Python, you should have:

  • Python 3.8 or a later version installed

  • Basic Python knowledge: Variables, data types, functions, and control structures

  • Understanding of data structures: Dictionaries, lists, and nested data structures

  • File handling basics: Reading from and writing to files in Python

  • Command line familiarity: Running Python scripts and installing packages with pip

You'll also need to install the PyYAML library:

pip install pyyaml

Table of Contents

  1. What Is YAML and Why Should You Care?

  2. How to Read YAML Files

  3. How to Write YAML Files

  4. How to Work with Lists in YAML

  5. Build a YAML Config Manager

What Is YAML and Why Should You Care?

YAML (YAML Ain't Markup Language) is a data serialization format designed to be easy to read and write. Think of it as JSON's more readable cousin. :)

Here's the same data in JSON and YAML:

JSON:

{
  "database": {
    "host": "localhost",
    "port": 5432,
    "credentials": {
      "username": "admin",
      "password": "secret"
    }
  }
}

YAML:

database:
  host: localhost
  port: 5432
  credentials:
    username: admin
    password: secret

The YAML version is cleaner and easier to read, especially for configuration files.

How to Read YAML Files

Let's say you have a configuration file for a web application. We'll create a simple config.yaml file and learn how to read it in Python.

First, let's understand what we're trying to do. You have configuration data stored in a YAML file, and you want to load it into Python so you can use it in your application. Here’s how you can do it:

import yaml

# Open and read the YAML file
with open('config.yaml', 'r') as file:
    config = yaml.safe_load(file)

# Access the data
print(config['database']['host'])

Output:

localhost

Here's what's happening in this code:

  • We import the yaml module.

  • Then we open the file using a context manager (with statement), which automatically closes the file when we're done.

  • We use yaml.safe_load() to parse the YAML content into a Python dictionary so we can access the data just like any Python dictionary.

⚠️ Note that you should always use yaml.safe_load() instead of yaml.load(). The safe_load() function protects you from arbitrary code execution vulnerabilities. Unless you have a very specific reason (and you probably don't), stick with safe_load().

How to Write YAML Files

Now let's go in the opposite direction. You have Python data structures and you want to save them as YAML files. This is useful when you're generating configuration files or exporting data.

Here's a practical example: imagine you're building a tool that generates configuration files for different environments (development, staging, production):

import yaml

# Your configuration data as Python dictionaries
config = {
    'database': {
        'host': 'localhost',
        'port': 5432,
        'name': 'myapp_db',
        'credentials': {
            'username': 'admin',
            'password': 'secret123'
        }
    },
    'server': {
        'host': '0.0.0.0',
        'port': 8000,
        'debug': True
    },
    'features': {
        'enable_cache': True,
        'cache_ttl': 3600
    }
}

# Write to a YAML file
with open('generated_config.yaml', 'w') as file:
    yaml.dump(config, file, default_flow_style=False)

Let's break down what's happening:

  • We create a nested Python dictionary with our configuration.

  • We open a file in write mode ('w').

  • We use yaml.dump() to convert the Python dictionary to YAML format and write it to the file.

  • The default_flow_style=False parameter ensures the output uses block style (the readable, indented format) instead of inline style.

The resulting generated_config.yaml file will be properly formatted and ready to use.

How to Work with Lists in YAML

YAML handles lists elegantly, and they're common in configuration files. Let's look at a practical example: managing a list of API endpoints.

Suppose you're building a microservices application and need to configure multiple service endpoints. Here's how you'd work with that data:

import yaml

# Configuration with lists
services_config = {
    'services': [
        {
            'name': 'auth-service',
            'url': 'http://auth.example.com',
            'timeout': 30
        },
        {
            'name': 'payment-service',
            'url': 'http://payment.example.com',
            'timeout': 60
        },
        {
            'name': 'notification-service',
            'url': 'http://notification.example.com',
            'timeout': 15
        }
    ],
    'retry_policy': {
        'max_attempts': 3,
        'backoff_seconds': 5
    }
}

# Write to file
with open('services.yaml', 'w') as file:
    yaml.dump(services_config, file, default_flow_style=False, sort_keys=False)

# Read it back
with open('services.yaml', 'r') as file:
    loaded_services = yaml.safe_load(file)

# Access list items
for service in loaded_services['services']:
    print(f"Service: {service['name']}, URL: {service['url']}")

Output:

Service: auth-service, URL: http://auth.example.com
Service: payment-service, URL: http://payment.example.com
Service: notification-service, URL: http://notification.example.com

This code helps us understand a few key concepts.

We can nest lists and dictionaries freely in our Python data structures. The sort_keys=False parameter preserves the order of keys as we defined them. When we read the YAML back, we can iterate over lists just like any Python list. The data structures in Python match the structures in YAML.

Build a YAML Config Manager

Let's put everything together with a practical example. We'll build a simple configuration manager class that handles environment-specific configs (a common need in real projects):

import yaml
import os

class ConfigManager:
    def __init__(self, config_dir='configs'):
        self.config_dir = config_dir
        self.config = {}

    def load_config(self, environment='development'):
        """Load configuration for a specific environment"""
        config_file = os.path.join(self.config_dir, f'{environment}.yaml')

        try:
            with open(config_file, 'r') as file:
                self.config = yaml.safe_load(file)
            print(f"✓ Loaded configuration for {environment}")
            return self.config
        except FileNotFoundError:
            print(f"✗ Configuration file not found: {config_file}")
            return None
        except yaml.YAMLError as e:
            print(f"✗ Error parsing YAML: {e}")
            return None

    def get(self, key_path, default=None):
        """Get a configuration value using dot notation"""
        keys = key_path.split('.')
        value = self.config

        for key in keys:
            if isinstance(value, dict) and key in value:
                value = value[key]
            else:
                return default

        return value

    def save_config(self, environment, config_data):
        """Save configuration to a file"""
        config_file = os.path.join(self.config_dir, f'{environment}.yaml')

        os.makedirs(self.config_dir, exist_ok=True)

        with open(config_file, 'w') as file:
            yaml.dump(config_data, file, default_flow_style=False)

        print(f"✓ Saved configuration for {environment}")

This ConfigManager class shows you how to build a practical utility:

  1. Initialization: We set up a directory for config files.

  2. Loading: The load_config() method reads environment-specific YAML files with proper error handling.

  3. Accessing data: The get() method lets you access nested values using dot notation (like 'database.host').

  4. Saving: The save_config() method writes configuration data to YAML files.

This is the kind of pattern you might actually use in projects. You can extend it further by adding validation, environment variable overrides, or configuration merging. Here’s how you can use the ConfigManager class we’ve coded:

if __name__ == '__main__':
    # Create config manager
    config_mgr = ConfigManager()

    # Create a sample development config
    dev_config = {
        'database': {
            'host': 'localhost',
            'port': 5432,
            'name': 'dev_db'
        },
        'api': {
            'base_url': 'http://localhost:8000',
            'timeout': 30
        }
    }

    # Save it
    config_mgr.save_config('development', dev_config)

    # Load and use it
    config_mgr.load_config('development')
    print(f"Database host: {config_mgr.get('database.host')}")
    print(f"API timeout: {config_mgr.get('api.timeout')}")

Running the above code should give you the following output:

✓ Saved configuration for development
✓ Loaded configuration for development
Database host: localhost
API timeout: 30

Conclusion

YAML is a powerful tool in your developer toolkit. It comes in handy when you’re configuring applications, defining CI/CD pipelines, or working with infrastructure as code.

In this article, you learned how to work with YAML files in Python. You can read configuration files, write data to YAML format, handle lists and nested structures, and build practical utilities like the ConfigManager we coded.

Start small. Try replacing a JSON config file in one of your projects with YAML. You'll quickly appreciate how much more readable it is, and you'll be comfortable working with YAML across the tools and platforms that use it.

Happy coding!



Read the whole story
alvinashcraft
1 minute ago
reply
Pennsylvania, USA
Share this story
Delete

ClickOnce Deployment Template for .NET 10

1 Share

ClickOnce Deployment Template for .NET 10

I’ve added a new Azure DevOps YAML template for deploying .NET 10 ClickOnce applications to network shares.

Overview

The new clickOnceExample.yml shows how to use the templates clickOnceNetBuildAndTest.yml and clickOnceEnvironmentPublish.yml to mimic the Visual Studio publish and streamline the process of building, testing, and deploying ClickOnce desktop applications using modern .NET CLI tools in Azure DevOps pipelines to a newtork drive.

I started from Ramesh Kanjinghat’s post , then upgraded it to .Net 10, removed the Migrations, added versioning, added the App.config to set the environment and split out to reusable templates. GitHub Copilot was helpful in this process.

Read the whole story
alvinashcraft
1 minute ago
reply
Pennsylvania, USA
Share this story
Delete

How to Implement OTP Authentication in Rust with Twilio

1 Share
Twilio posts cloud communications trends, customer stories, and tips for building scalable voice and SMS applications with Twilio's APIs.
Read the whole story
alvinashcraft
1 minute ago
reply
Pennsylvania, USA
Share this story
Delete

How to Create a GraphQL Server in Go with GqlGen

1 Share
Twilio posts cloud communications trends, customer stories, and tips for building scalable voice and SMS applications with Twilio's APIs.
Read the whole story
alvinashcraft
2 minutes ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories