org-babel Support for C# source blocks
Org Mode support for
C#
Introduction
Babel can compile and evaluate C# code blocks with this module. It is different from ob-csharp in org-contrib in that it allows for significantly more complex setups, which is outlined in the following. A C# code-block
- will be compiled in the context of a csproj-file
- it can have dependencies on
- other projects (define in csproj-files)
- compiled assemblies (*.dll-files)
- nuget packages (custom nuget feeds can be configured)
- can be compiled with arbitrary .NET SDKs
- allows for arbitrary project settings
Requirements and Setup
A version of .NET should be installed on the host system that is executing the C# code blocks.
Please make sure that the target framework configured in the variable org-babel-csharp-default-target-framework
is available on your system. Currently, this variable is set to the most recent discoverable .NET SDK found on the host system.
If that is not what you intend as a default, this variable can be configured e.g. like
(setf org-babel-csharp-default-target-framework "net8.0")
Org Mode Features for C#
Configuration
There are a few global variables that will have effect on every C# code block.
org-babel-csharp-compiler
- sets the compiler that will be used to compile the code block's csproj file. Default is
dotnet
. org-babel-csharp-default-target-framework
- depending on the installed .NET SDKs (get a list by calling
dotnet --list-sdks
), this variable can be set as desired. It will be used as the default when no:framework
header argument was given. If not configured, the most recent discoverable version will be used. org-babel-csharp-additional-project-flags
- if the default "minimal viable" csproj configuration does not fit your needs, you can add arbitrary additional configuration here. When not
nil
it will be put in the mainPropertyGroup
section of the csproj file. Note that it must be in "encoded" in a valid XML syntax as it will be used as-is. You can for example set the build configuration of the code-block to Release with(setq org-babel-csharp-additional-project-flags "<Configuration>Release</Configuration>")
You might want to reset some of these variables to their defaults in case you need different, or default, configuration in subsequent code blocks.
Header arguments
:main
- defines whether or not to wrap the code block's content in a default
void Main(string[] args)
function. Anything other than"no"
(including no:main
argument at all) as a value for this argument will result in wrapping the code block in aMain
function when expanding. :nugetconfig
- path to a custom NuGet.config file to use for the code block. This is useful if you have a local *.nupkg or a feed different from nuget.org and want to use it in your source block.
:framework
- the target framework of the code block. See
org-babel-csharp-default-target-framework
for permanent configuration of this. :class
- defines the name of the program wrapper class. It accepts string arguments. If left empty, it will use the default name
Program
as a program wrapper class name. A value of"no"
will inhibit a wrapper class. :references
- accepts a list of dependencies. Three types of references are allowed
- Project References can be passed using a full or relative path to an existing project. To set a project reference, the file path will need a
.csproj
file extension. Example::references '( "/home/me/otherproject/theproject.csproj")
- Assembly Reference can be passed using a full or relative path to an existing assembly. To set an assembly reference, the file path will need a
.dll
file extension. Example::references '( "/home/me/otherproject/theproject.dll")
- Nuget Package Reference can be passed by simply adding the correct name of the package to the reference list. In order for this to work, the
org-babel-csharp-nuget-config
variable should define a nuget feed from where this package can be fetched. To set a nuget reference, don't add a file extension to the dependency. In case you want a package with a specific version, pass the respective package as a cons cell where the cdr is a string representing the desired version. Example::references '(Newtonsoft.Json)
will give you any version of Newtonsoft.Json,:references '(("Newtonsoft.Json" . "13.0.3"))
will give you version 13.0.3 of that package.
- Project References can be passed using a full or relative path to an existing project. To set a project reference, the file path will need a
:usings
- a list of namespaces to include in the using block of the resulting .cs file. This is
- a convenience feature when you set
:class "no" :main "no"
(as you could typeusing project.a.featureb;
at the start of the code block) - a necessity if the main function and the wrapper class are generated automatically and you need to pass in namespace dependencies (and don't want to write fully qualified names for usages of the respective external dependencies)
- a convenience feature when you set
:cmdline
- command line arguments that will be passed to the compiled executable of the respective source block.
Examples
Minimal Viable C# Code Block
Simply adding a new code block and setting its language to csharp
is sufficient.
The following will compile and evaluate (as an <OutputType>Exe</OutputType>
project).
#+begin_src csharp Console.WriteLine("Hello from C#"); #+end_src
Hello from C#
Custom Class
By default, the code within the code block is wrapped in a class called Program
. This name can be configured in with the :class
header argument. Whether or not an automated Main
function should be created can be configured in the :main
header argument (everything else than "no" will result in an automatic main function).
The generated code will be put in a temporary directory governed by org-babel-temporary-directory
.
The following code block will be
- wrapped in a class
MyTest
- completely wrapped in a
static void Main(string[] args)
function
#+begin_src csharp :class "MyClass" :usings '("System" "System.Diagnostics" "System.Reflection") :main yes string projectName = Assembly.GetCallingAssembly().GetName().Name; string projectDirectory = "my-test"; var stackTrace = new StackTrace(); var firstStackFrame = stackTrace.GetFrame(0); string methodName = firstStackFrame.GetMethod().ToString(); string className = firstStackFrame.GetMethod().DeclaringType.ToString(); Console.WriteLine($"Directory:\t{projectDirectory}"); Console.WriteLine($"Project:\t{projectName}"); Console.WriteLine($"Class:\t{className}"); Console.WriteLine($"Method:\t{methodName}"); #+end_src
Dll Name: | obcsRELp52.dll |
Project: | obcsRELp52 |
Class: | obcsi7SYVW.MyClass |
Method: | Void Main(System.String[]) |
Input Variables
Idiomatic variable types are detected automatically. The following code block indicates the correct detection of
System.Int32
System.Double
System.String
#+begin_src csharp :var a=3 b="pizza" c=5.3 d=-1 int myInt = 1; string PrintTypeVal<T>(T tp) { return $"{tp.GetType().ToString()} {tp}"; } var myString = $"{PrintTypeVal(myInt)}\n{PrintTypeVal(a)}\n{PrintTypeVal(b)}\n{PrintTypeVal(c)}\n{PrintTypeVal(d)}"; Console.WriteLine(myString); #+end_src
System.Int32 | 1 |
System.Int32 | 3 |
System.String | pizza |
System.Double | 5.3 |
System.Int32 | -1 |
Output Formatting
In the above code blocks, tabular output was used implicitly. List output is supported as well but must be specified like so
#+begin_src csharp :results raw list Console.WriteLine("Item 1"); Console.WriteLine("Item 2"); Console.WriteLine("Item 3"); Console.WriteLine("Item 4"); #+end_src
- Item 1
- Item 2
- Item 3
- Item 4
NuGet References
With the following code block, we create a string serialization based on the Newtonsoft.Json NuGet package. Since this is available from nuget.org, no additional configuration is needed.
#+begin_src csharp :references '(("Newtonsoft.Json" . "13.0.3")) :usings '("System" "Newtonsoft.Json") :main no :project "json-test" :results raw public class DTO { public int TheInt { get; set; } public string TheString { get; set; } } static void Main(string[] args) { DTO myDto = new() { TheInt = 12, TheString = "ok" }; string json = JsonConvert.SerializeObject(myDto, Formatting.Indented); Console.WriteLine($"{json}"); } #+end_src
{ "TheInt": 12, "TheString": "ok" }