C#
I’m tired of having to look up simple things that I can’t immediately remember.
Configuration
Given this example appsettings.json
{
"Position": {
"Title": "Editor",
"Name": "Joe Smith"
}
}
Access in Program/Startup
Accessing configuration info in builder
var test = builder.Configuration["Position:Title"];
Console.Writeline(test);
IOptions Pattern
Matching class
public class PositionOptions
{
public const string Name = "Position";
public string Title { get; set; } = String.Empty;
public string Name { get; set; } = String.Empty;
}
binding in program.cs
builder.Services.Configure<PositionOptions>(
builder.Configuration.GetSection(PositionOptions.Name));
dependency injection
private readonly PositionOptions _positionOptions;
public MyController(IOptions<PositionOptions> positionOptions)
{
_positionOptions = positionOptions.Value;
}
Date and Time
The new standard for this in .NET is called DateTimeOffset. It is like DateTime, but with a time zone attached.
// Declare specific date and time
var d = new DateTimeOffset(2023, 2, 8, 10, 00, 00).AddDays(120);
// Old style DateTime
var d = new DateTime(2023, 2, 8, 10, 00, 00);
// Date only
var c = new DateOnly(2023, 6, 8);
Console.WriteLine(c.AddDays(-30).ToShortDateString());
// Parse from ISO
DateTime.ParseExact("2023-04-14T15:24:22.3552219-05:00", "o", CultureInfo.InvariantCulture);
// Print ISO
Console.WriteLine(b.ToString("F"));
// Few ways to get string of month; 13th month is blank string. There is no enum.
CultureInfo.CurrentCulture.DateTimeFormat.MonthNames.First();
CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(DateTime.Now.Month);
DateTimeFormatInfo.CurrentInfo.MonthNames.First();
DateTimeFormatInfo.CurrentInfo.GetMonthName(DateTime.Now.Month);
// Day of week enum
System.DayOfWeek.Monday
Leap year bug
This will throw an exception when a leap day comes around!
var nextYear = new DateTime(current.Year + 1, current.Month, current.Day);
Be sure to instead use the built in method.
var nextYear = current.AddYears(1);
Time Zone
// TBD? Probably just use nodatime
File I/O
Read Entire File
var path = @"c:\temp\MyTest.txt";
if (!File.Exists(path))
{
File.WriteAllLines(path, "new text, ");
File.AppendAllText(path, "appended text");
File.ReadAllLines(path);
}
Read by Line
var workingDirectory = new DirectoryInfo(Directory.GetCurrentDirectory());
var path = Path.Combine(
workingDirectory?.FullName ?? throw new DirectoryNotFoundException(),
"file.txt"
);
using var fileStream = File.OpenRead(path);
using var streamReader = new StreamReader(fileStream);
var data = new List<string>();
string? line;
while ((line = streamReader.ReadLine()) != null)
{
data.Add(line);
}
XML Parsing
using System.Xml;
using System.Xml.Linq;
using var readStream = File.OpenRead(path);
using XmlReader reader = XmlReader.Create(readStream, new XmlReaderSettings
{
IgnoreComments = false
});
while (reader.Read())
{
switch (reader.NodeType)
{
case XmlNodeType.Element:
var data = XElement.Parse(reader.Value);
break;
case XmlNodeType.Text:
break;
case XmlNodeType.CDATA:
break;
default:
break;
}
}
LINQ
// TBD
Strings
String Linting by Language
In .NET 7, you can inform Visual Studio about what your strings are using a decorator. Doing so will actually give you convenient intellisense.
void DoSomething(DateTime dateTime,
[StringSyntax(StringSyntaxAttribute.DateTimeFormat)] string format)
{
// do something
}
DoSomething(DateTime.Now, "");
// ^ here you will have intellisense on datetime formats!
DateTime.Now.ToString("");
// ^ ToString() is now overridden to provide this as well
[StringSyntax(StringSyntaxAttribute.Json)]
var myJson = """
[{
"test": "test"
}]
"""
If you are using .NET 6 or lower, you can accomplish the same result with the uglier but still as effective language comment.
var myRegex = /*lang=regex*/ "";
var myJson = /*lang=json,strict*/ @"{""test"":""test""}";
String Literal Variants
Composite
This is the old way and should only be used for logging. Alignment of tabular data is also possible.
string.Format("The time is {0}", DateTime.Now)
Interpolation
The preferred way mostly. Alignment also possible.
$"The time is {DateTime.Now}"
Verbatim
For when your string will require many escaped characters. Allows for multiline strings. You still have to escape double quotes, however.
@"c:\documents\files\u0066.txt"
@"He said, ""This is the last \u0063hance\x0021"""
Verbatim Interpolated
To interpolate your verbatim string. Can use both @$ or $@ to indicate.
$@"The time is
{DateTime.Now}"
Raw
Even easier multiline strings. No escaped characters and includes whitespace and new lines. New lines at the beginning and end are trimmed, however.
"""
This is a multi-line
string literal with the second line indented.
"""
Raw Interpolated
To interpolate your raw string. Also allows printing braces with interpolation.
$"""The point "{X}, {Y}" is {Math.Sqrt(X * X + Y * Y)} from the origin"""
$$"""The point {{{X}}, {{Y}}} is {{Math.Sqrt(X * X + Y * Y)}} from the origin"""
UTF-8
.NET strings are UTF-16 by default, but UTF-8 is the standard for web. They are not compile time and cannot be interpolated.
"AUTH "u8
Exceptions
Never throw caught exceptions!
try
{
throw new Exception();
}
catch (Exception e)
{
throw e; // Don't do this!!
}
Do this instead:
catch (Exception e)
{
_logger.Error("Error: ", e);
throw; // Note the missing parameter!
}
Calling throw; without passing in an exception as an argument acts as a handoff; the original exception simply continues to bubble up the stack. Passing the exception as an argument creates a new exception with only some of the data in the original exception.
Why is this bad?
Because the newly created exception does not contain the complete call stack. You are losing valuable information!
Furthermore, if you have nothing add, just don’t catch the exception.
Always throw exceptions as far down the stack as possible. Always catch exceptions as far up the stack as possible.
This will provide the most information and context to exactly what went wrong and allow you to handle the exception with the best judgement.
Signatures
Parameters should be as as generic as possible, and return types should be as specific as possible. Don’t do this:
public IEnumerable<int> Calculate(List<int> nums) { ... }
Do this:
public List<int> Calculate(IEnumerable<int> nums) { ... }
TODO: explain
Access Modifiers
An assembly is a .dll or .exe created by compiling one or more .cs files in a single compilation. In short, another project.
| Caller’s location | public |
protected internal |
protected |
internal |
private protected |
private |
|---|---|---|---|---|---|---|
| Within the class | ✔️️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| Derived class | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ |
| Non-derived class | ✔️ | ✔️ | ❌ | ✔️ | ❌ | ❌ |
| Derived class, different assembly | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ❌ |
| Non-derived class, different assembly | ✔️ | ❌ | ❌ | ❌ | ❌ | ❌ |
- Classes can only be
publicorinternal. - Structs don’t support inheritance, so they and their members cannot be marked
protected. - Members are not greater in accessibility than their class unless overriding virtual methods in a public base class.
Autoprops
Mutable
Old school:
class Point
{
private int _x;
public int X
{
get { return _x; }
set { _x = value; }
}
private int _y;
public int Y
{
get { return _y; }
set { _y = value; }
}
}
Auto-Implemented Properties:
class Point
{
public int X { get; set; }
public int Y { get; set; }
}
Immutable
Old school:
class Point
{
private int _x;
public int X
{
get { return _x; }
}
private int _y;
public int Y
{
get { return _y; }
}
public Point(int x, int y)
{
this._x = x;
this._y = y;
}
}
Auto-Implemented Properties:
class Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y)
{
this.X = x;
this.Y = y;
}
}
Init Only Setter:
class Point
{
public int X { get; init; }
public int Y { get; init; }
}
Using the init only setter, we have the option to create immutable objects like this along with the standard constructor option:
var p = new Point() { X = 42, Y = 13 };
Methods from Object
Here is some boilerplate for overriding the standard methods inherited from Object.
public override string ToString()
{
return Value;
}
public override int GetHashCode()
{
return Value.GetHashCode();
}
public override bool Equals(object? obj)
{
if (obj == null)
{
return false;
}
if (ReferenceEquals(this, obj))
{
return true;
}
var item = obj as Status;
if (item == null)
{
return false;
}
return Value.Equals(item.Value);
}
public static bool operator == (Status a, Status b)
{
if (a is null)
{
return b is null;
}
return a.Equals(b);
}
public static bool operator != (Status a, Status b)
{
return !(a == b);
}