A small scoped and modular ecosystem of packages that intend to provide quality of life improvements for C# developers. Each package provides these improvements within a domain of work, so that you can keep your references light and tailored to your needs.
- Sharpener
The Sharpener
package focuses on features for the basic runtime layer.
- Concise syntax for string comparisons
- reducing code clutter due to
StringComparison.SomethingIgnoreCase
spam.
- reducing code clutter due to
- Array extensions
Add
,AddRange
,Remove
, andRemoveAll
.- Faster than converting to
List<T>
in one-off situations for large arrays.
- Faster than converting to
- Fast version of
ForEach
available to allIEnumerable
types usingSpan<T>
. - A variation from
Join
on lists that allows left joins calledLeftJoin
. As
variants ofToList
andToArray
that will prefer to try casting before creating.- If your IDE gives you "multiple enumeration warnings", this one is worth looking at. See Extended Features for the other extended packages in the Sharpener ecosystem.
Navigate to the directory of your .csproj file and then run this command
dotnet add package Sharpener
or
PM> Install-Package Sharpener
var name = "Rick Astley";
var isRickRoll = name.NoCase().StartsWith("rick")
Has culture builders as well like Current()
, Invariant()
, and Ordinal()
.
Say you are generating a lookup key based on the values of a type. While some of those values are nullable, like strings, you need every property to generate something. You can now use myString.OrFallback()
and if the string was null or whitespace, you'll end up with "null". If you require a different fallback, you can either use myString.OrFallback("myFallback")
or you can change the default fallback by changing the SharpenerStringsSettings.DefaultFallback
value.
For any implementation of IEnumerable<T>
, you can now call myCollection.IsNullOrEmpty()
instead of gymnastics like myCollection?.Any() != true
. It will also check types to be sure that the parameter is not already an array or collection, which should have a flattened Count
or Length
value to use already. Failing that, only then does it iterate to check if there are values within.
It's not uncommon to have the need for breaking a large collection up into a collection of smaller collections. Especially good for work that requires transactions, save points, and so on. You can now use myCollection.ToBatches(30)
to have a collection of arrays with 30 members.
Since Sharpener is now compatible with .NET Standard 2.0 and above, this makes this highly useful extension available for versions prior to .NET 6. It is not generated for .NET 6 and above, so there should be no conflicts with the built in method when it's available out of the box.
itemsArray = itemsArray.Add(newItem);
These extensions are a better choice than converting to List
when the array will be rarely manipulated. Even more so
when the array is large. But when manipulations of the array will be common, conversion to a List
is more appropriate.
They create a new array using Span<T>
for high performance, which means that you must re-assign the output to either a
new variable or the original one. This is unlike the List
operations, where the list is changed by the call.
Say you have an IEnumerable<Student>
and you need to reset their Absences
on all.
students.ForAll(student => student.Absences = 0);
You don't need to cast it as a list and it will run faster with lower memory than if you called ToList
and
then ForEach
.
Say you have that same enumerable from the above example. It has been passed down from method to method and you don't
know, off hand, if it is a list or not.
You perform two operations that may enumerate it twice, so your IDE warns you about this. If it is already a list,
calling ToList
will make another list from your list. What do you do to settle this warning?
var studentList = students.AsList();
var aStudents = studentList.Select(student => student.Grade == "A");
var bStudents = studentList.Select(student => student.Grade == "B");
var cStudents = studentList.Select(student => student.Grade == "C");
var dStudents = studentList.Select(student => student.Grade == "D");
var eStudents = studentList.Select(student => student.Grade == "E");
var fStudents = studentList.Select(student => student.Grade == "F");
Granted, GroupBy
is the better option here, but you get the point.
Say that you have two lists and you need to quickly find relationships between the two. For this situation, I've seen a lot of code that looks something like this.
var studentCandidates = new List<Student>();
foreach(var student in students)
{
foreach(var candidate in candidates)
{
if(student.Name.Equals(candidate.RegisteredName))
{
studentCandidates.Add(student);
}
}
}
Just to find which students are also candidates. Now you could be more succinct.
var studentCandidates = students
.Where(student => candidates.Any(candidate => student.RegisteredName.Equals(candidate.Name)));
But consider that you are iterating through the students list for every entry in the candidates list. That's brutal.
Now, not to overlook the value of the Intersect
and Union
extensions, there are also join clauses for situations
where it's not assumed that equality will be determined by the default equality comparer.
.NET allows you to write code like this, for example.
var studentCandidates = from student in students
join candidate in candidates on student.Name equals candidate.RegisteredName
select student;
The performance of the above code is going to be magnitudes of order improved upon the other multiple iteration techniques. Keep that in mind for general knowledge. It's vastly underused. But not everyone likes the query syntax. If you want this, but in Linq syntax, .Net also offers this.
var studentCandidates = students.Join(candidates,
student => student.Name,
candidate => candidate.RegisteredName,
(student, candidate) => student);
But there's one issue. What if you want a left join
. A join where, when no equality is found, you still get the value
from the first (students) list so that you know that it did not have a match. There are times where you'll need this.
The query syntax offers into
and DefaultIfEmpty
for it.
var leftJoin = from student in students
join candidate in candidates on student.Name equals candidate.RegisteredName into joins
from joined in joins.DefaultIfEmpty()
select new {Student = student, Candidate = joined};
var studentCandidates = leftJoin.Where(pair => pair.Candidate is not null);
But to have this in Linq syntax, Join
doesn't really offer much there. Now, you can use GroupJoin
to achieve this
with Linq, but it's a much more complex lambda structure than one really needs for a simple left join. So now you can
simply repeat the previous Join
sample, but replace it with LeftJoin
and you'll receive results where the second (
candidates) had no match.
var leftJoin = students.LeftJoin(candidates,
student => student.Name,
candidate => candidate.RegisteredName,
(student, candidate) => new {Student = student, Candidate = candidate});
var studentCandidates = leftJoin.Where(pair => pair.Candidate is not null);
The baked-in tooling for writing web requests can be ugly, verbose, and difficult to test. This is why there are many libraries like RestSharp and Flurl that offer new and more manageable ways to write requests. Influenced by these, but with intent to keep our footprint and framework shallow, so that you are staying closer to the fundamental .NET level, this library offers you a simple and basic set of features for fluent request syntax.
var result = await httpClient.Rest("https://nice-service.com/api")
.SetPaths("accounts", "create")
.SetBearerToken(token)
.AddQueries(new
{
Expired = false,
Owner = true,
})
.SetJsonContent(createAccount)
.PostAsync()
.ReadJsonAs<CreatedAccount>();
var createdAccount = result.IsSuccess
? result.Value
: // handle failures
Please open an issue for support.
Please contribute using Github Flow. Create a branch, add commits, and open a pull request.