Rick's Dev Notes

Version 0.3.3

Dev notes you can use

Last updated on Fri, 09 Feb 2024 23:02 UTC

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 public or internal.
  • 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);
}