Automate Document Signing using the DocuSign API
Adam Yue
Sept. 29, 2020
/
The productivity features
There are heaps of development tools for C#:
Visual Studio is the most powerful one. The community version is free for non-commercial company.
You can download latest version of Visual Studio Community version for free here: https://visualstudio.microsoft.com/
You can download latest version of Visual Studio Code for free here: https://code.visualstudio.com/
You can download latest version of Visual Studio For Mac for free here: https://visualstudio.microsoft.com/vs/mac/
nameof()
In the past, we have composite formatting:
Console.WriteLine("Hello, {0}! Today is {1}, it's {2:HH:mm} now.", name, date.DayOfWeek, date);
Now we can do this with string interpolation. The $
special character identifies a string literal as an interpolated string:
Console.WriteLine($"Hello, {name}! Today is {date.DayOfWeek}, it's {date:HH:mm} now.");
And of course, with advanced formatting:
Console.WriteLine($"On {date:d}, the price of {item.Name} was {item.Price:C2} per {item.perPackage} items");
Exception Filters are clauses that determine when a given catch clause should be applied:
WebRequestHandler webRequestHandler = new WebRequestHandler();
webRequestHandler.AllowAutoRedirect = false;
using (HttpClient client = new HttpClient(webRequestHandler))
{
var stringTask = client.GetStringAsync("https://docs.microsoft.com/en-us/dotnet/about/");
try
{
var responseText = await stringTask;
return responseText;
}
catch (System.Net.Http.HttpRequestException e) when (e.Message.Contains("301"))
{
return "Site Moved";
}
}
nameof()
The nameof expression evaluates to the name of a symbol, so that you don't need to hardocode it as string, and that will help code rectoring logic:
if (IsNullOrWhiteSpace(lastName))
throw new ArgumentException(message: "Cannot be blank", paramName: nameof(lastName));
out var
out var
You can now declare out variables in the argument list of a method call, rather than writing a separate declaration statement.
Before:
if (int.TryParse(input, out int result))
Console.WriteLine(result);
else
Console.WriteLine("Could not parse input");
Now:
if (int.TryParse(input, out var result))
Console.WriteLine(result);
else
Console.WriteLine("Could not parse input");
If you call a method with out
parameters but don't want to define a variable as you don't care, use discards. A discard is a write-only variable whose name is _
(the underscore character). A discard is like an unassigned variable:
public static void Main()
{
var (_, _, _, pop1, _, pop2) = QueryCityDataForYears("New York City", 1960, 2010);
Console.WriteLine($"Population change, 1960 to 2010: {pop2 - pop1:N0}");
}
private static (string, double, int, int, int, int) QueryCityDataForYears(string name, int year1, int year2)
{
int population1 = 0, population2 = 0;
double area = 0;
if (name == "New York City")
{
area = 468.48;
if (year1 == 1960)
{
population1 = 7781984;
}
if (year2 == 2010)
{
population2 = 8175133;
}
return (name, area, year1, population1, year2, population2);
}
return ("", 0, 0, 0, 0, 0);
}
If you need a simple structure containing more than one data element, use tuples, which are lightweight data structures that contain multiple fields to represent the data members.
(string Alpha, string Beta) namedLetters = ("a", "b");
Console.WriteLine($"{namedLetters.Alpha}, {namedLetters.Beta}");
With name:
var alphabetStart = (Alpha: "a", Beta: "b");
Console.WriteLine($"{alphabetStart.Alpha}, {alphabetStart.Beta}");
Deconstruct:
var (Alpha, Beta) namedLetters = ("a", "b");
Console.WriteLine($"{Alpha}, {Beta}");
Local method is only accessible from the context in which it is declared.
public static IEnumerable<char> AlphabetSubset3(char start, char end)
{
if (start < 'a' || start > 'z')
throw new ArgumentOutOfRangeException(paramName: nameof(start), message: "start must be a letter");
if (end < 'a' || end > 'z')
throw new ArgumentOutOfRangeException(paramName: nameof(end), message: "end must be a letter");
if (end <= start)
throw new ArgumentException($"{nameof(end)} must be greater than {nameof(start)}");
return alphabetSubsetImplementation();
IEnumerable<char> alphabetSubsetImplementation()
{
for (var c = start; c < end; c++)
yield return c;
}
}
An expression-bodied member can be used with 1 liner code for both methods and read-only properties.
Readonly property:
public string FullName => $"{FirstName} {LastName}";
// Expression-bodied constructor
public ExpressionMembersExample(string label) => this.Label = label;
// Expression-bodied finalizer
~ExpressionMembersExample() => Console.Error.WriteLine("Finalized!");
private string label;
// Expression-bodied get / set accessors.
public string Label
{
get => label;
set => this.label = value ?? "Default label";
}
The switch statement was a pattern expression which supports the constant pattern, then later added support for numbers and string:
public static string GenerateMessage(params string[] parts)
{
switch (parts.Length)
{
case 0:
return "No elements to the input";
case 1:
return $"One element: {parts[0]}";
case 2:
return $"Two elements: {parts[0]}, {parts[1]}";
default:
return $"Many elements. Too many to write";
}
}
But now, it supports any type:
public static double ComputeAreaModernSwitch(object shape)
{
switch (shape)
{
case Square s:
return s.Side * s.Side;
case Circle c:
return c.Radius * c.Radius * Math.PI;
case Rectangle r:
return r.Height * r.Length;
default:
throw new ArgumentException(
message: "shape is not a recognized shape",
paramName: nameof(shape));
}
}
And even with when clause:
public static double ComputeArea_Version4(object shape)
{
switch (shape)
{
case Square s when s.Side == 0:
case Circle c when c.Radius == 0:
case Triangle t when t.Base == 0 || t.Height == 0:
case Rectangle r when r.Length == 0 || r.Height == 0:
return 0;
case Square s:
return s.Side * s.Side;
case Circle c:
return c.Radius * c.Radius * Math.PI;
case Triangle t:
return t.Base * t.Height / 2;
case Rectangle r:
return r.Length * r.Height;
default:
throw new ArgumentException(
message: "shape is not a recognized shape",
paramName: nameof(shape));
}
}
In the past, we initialize a variable with a default value:
Func<string, bool> whereClause = default(Func<string, bool>);
Now, we can simply use the default
operator:
Func<string, bool> whereClause = default;
There are two new features to write numbers in the most readable fashion for the intended use: binary literals, and digit separators.
public const int SixtyFour = 0b0100_0000;
public const int OneHundredThousand = 100_000;
var using
??
ring a bell?)var using
A using declaration is a variable declaration preceded by the using
keyword. The compiler will dispose the variable at the end of the enclosing scope:
static int WriteLinesToFile(IEnumerable<string> lines)
{
using var file = new System.IO.StreamWriter("WriteLines2.txt");
// Notice how we declare skippedLines after the using statement.
int skippedLines = 0;
foreach (string line in lines)
{
if (!line.Contains("Second"))
{
file.WriteLine(line);
}
else
{
skippedLines++;
}
}
// Notice how skippedLines is in scope here.
return skippedLines;
// file is disposed here
}
You can mark a member readonly, like the Distance property below:
public struct Point
{
public double X { get; set; }
public double Y { get; set; }
public double Distance => Math.Sqrt(X * X + Y * Y);
public override string ToString() =>
$"({X}, {Y}) is {Distance} from the origin";
}
Existing switch is a statement, but now we have expression:
Imagine we have an enum:
public enum Rainbow
{
Red,
Orange,
Yellow,
Green,
Blue,
Indigo,
Violet
}
In the past we need to do like this:
public static RGBColor FromRainbowClassic(Rainbow colorBand)
{
switch (colorBand)
{
case Rainbow.Red:
return new RGBColor(0xFF, 0x00, 0x00);
case Rainbow.Orange:
return new RGBColor(0xFF, 0x7F, 0x00);
case Rainbow.Yellow:
return new RGBColor(0xFF, 0xFF, 0x00);
case Rainbow.Green:
return new RGBColor(0x00, 0xFF, 0x00);
case Rainbow.Blue:
return new RGBColor(0x00, 0x00, 0xFF);
case Rainbow.Indigo:
return new RGBColor(0x4B, 0x00, 0x82);
case Rainbow.Violet:
return new RGBColor(0x94, 0x00, 0xD3);
default:
throw new ArgumentException(message: "invalid enum value", paramName: nameof(colorBand));
};
}
Now we can:
public static RGBColor FromRainbow(Rainbow colorBand) =>
colorBand switch
{
Rainbow.Red => new RGBColor(0xFF, 0x00, 0x00),
Rainbow.Orange => new RGBColor(0xFF, 0x7F, 0x00),
Rainbow.Yellow => new RGBColor(0xFF, 0xFF, 0x00),
Rainbow.Green => new RGBColor(0x00, 0xFF, 0x00),
Rainbow.Blue => new RGBColor(0x00, 0x00, 0xFF),
Rainbow.Indigo => new RGBColor(0x4B, 0x00, 0x82),
Rainbow.Violet => new RGBColor(0x94, 0x00, 0xD3),
_ => throw new ArgumentException(message: "invalid enum value", paramName: nameof(colorBand)),
};
Index and range can be used to easily manipulate array. For array, the 0 index is the same as sequence[0]. The ^0 index is the same as sequence[sequence.Length]. Sequence[^0] throws an exception, just as sequence[sequence.Length] does. For any number n, the index ^n is the same as sequence.Length - n.
var words = new string[]
{
// index from start index from end
"The", // 0 ^9
"quick", // 1 ^8
"brown", // 2 ^7
"fox", // 3 ^6
"jumped", // 4 ^5
"over", // 5 ^4
"the", // 6 ^3
"lazy", // 7 ^2
"dog" // 8 ^1
}; // 9 (or words.Length) ^0
To get the last word:
Console.WriteLine($"The last word is {words[^1]}");
To get a range:
Range phrase = 1..4;
var text = words[phrase];
??
ring a bell?)We can use the ??=
operator to assign the value of its right-hand operand to its left-hand operand only if the left-hand operand evaluates to null.
List<int> numbers = null;
int? i = null;
numbers ??= new List<int>();
numbers.Add(i ??= 17);
numbers.Add(i ??= 20);
Console.WriteLine(string.Join(" ", numbers)); // output: 17 17
Console.WriteLine(i); // output: 17
It's not just an editor!
ctor
and then double press TabSo, what's next?
Address
Level 8
11-17 York Street
Sydney NSW 2000
Phone Number
+61 2 8294 8067
Email
[email protected]
By Adam Yue
By Felix Schmitz
© 2017-2024 Darumatic Pty Ltd. All Rights Reserved.