mirror of
https://github.com/wagesj45/CapyKit.git
synced 2024-12-21 21:02:30 -06:00
Adding Extensions and Helpers
This commit is contained in:
parent
cbbe897d15
commit
6cdd805be4
7 changed files with 686 additions and 7 deletions
|
@ -4,6 +4,7 @@ using System.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using CapyKit.Attributes;
|
||||||
using CapyKit.Extensions;
|
using CapyKit.Extensions;
|
||||||
|
|
||||||
namespace CapyKit
|
namespace CapyKit
|
||||||
|
@ -14,7 +15,7 @@ namespace CapyKit
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Because consumers of CapyKit may have varied ways of handling logging, the <see cref="CapyEventReporter"/> provides
|
/// Because consumers of CapyKit may have varied ways of handling logging, the <see cref="CapyEventReporter"/> provides
|
||||||
/// a way for subscribers to recieve events for various "events" within the library. These can be thought of as
|
/// a way for subscribers to recieve events for various "events" within the library. These can be thought of as
|
||||||
/// a logging solution for CapyKit.
|
/// a logging solution for CapyKit. Consumers are free to treat these events however they see fit.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public static class CapyEventReporter
|
public static class CapyEventReporter
|
||||||
{
|
{
|
||||||
|
@ -25,6 +26,10 @@ namespace CapyKit
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static Dictionary<EventLevel, List<(CapyEventHandler Handler, string origin)>> subscribers = new Dictionary<EventLevel, List<(CapyEventHandler Handler, string origin)>>();
|
private static Dictionary<EventLevel, List<(CapyEventHandler Handler, string origin)>> subscribers = new Dictionary<EventLevel, List<(CapyEventHandler Handler, string origin)>>();
|
||||||
|
|
||||||
|
/// <summary> A hash set storing unique identifiers for events intended to only be emitted once. </summary>
|
||||||
|
/// <seealso cref="EmitEventOnce(EventLevel, string, string, string, object[])"/>
|
||||||
|
private static HashSet<string> uniqueIdentifiers = new HashSet<string>();
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Methods
|
#region Methods
|
||||||
|
@ -73,14 +78,17 @@ namespace CapyKit
|
||||||
/// <summary> Emits an event with the given severity level, message, and method name. </summary>
|
/// <summary> Emits an event with the given severity level, message, and method name. </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// In order to allow for efficient calling member access via <see cref="CallerMemberNameAttribute"/>
|
/// In order to allow for efficient calling member access via <see cref="CallerMemberNameAttribute"/>
|
||||||
/// , it is suggested that <paramref name="args"/> is defined explicitly for formatted messages.
|
/// ,
|
||||||
|
/// it is suggested that <paramref name="args"/> is defined explicitly for formatted messages.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <param name="eventLevel"> The severity level of the event. </param>
|
/// <param name="eventLevel"> The severity level of the event. </param>
|
||||||
/// <param name="message"> The message describing the reason for the event. </param>
|
/// <param name="message">
|
||||||
/// <param name="method">
|
/// The message describing the reason for the event. String formatting for <paramref name="args"/>
|
||||||
/// (Optional) The name of the method where the event was raised. String formatting for <paramref name="args"/>
|
|
||||||
/// is accepted.
|
/// is accepted.
|
||||||
/// </param>
|
/// </param>
|
||||||
|
/// <param name="method">
|
||||||
|
/// (Optional) The name of the method where the event was raised.
|
||||||
|
/// </param>
|
||||||
/// <param name="args">
|
/// <param name="args">
|
||||||
/// A variable-length parameters list containing arguments for formatting the message.
|
/// A variable-length parameters list containing arguments for formatting the message.
|
||||||
/// </param>
|
/// </param>
|
||||||
|
@ -88,6 +96,7 @@ namespace CapyKit
|
||||||
/// CapyEventReporter.EmitEvent(EventLevel.Error, "Could not find the description for {0}.",
|
/// CapyEventReporter.EmitEvent(EventLevel.Error, "Could not find the description for {0}.",
|
||||||
/// args: new[] { enumeration });
|
/// args: new[] { enumeration });
|
||||||
/// </example>
|
/// </example>
|
||||||
|
/// <seealso cref="CallerMemberNameAttribute"/>
|
||||||
internal static void EmitEvent(EventLevel eventLevel, string message, [CallerMemberName] string method = null, params object[] args)
|
internal static void EmitEvent(EventLevel eventLevel, string message, [CallerMemberName] string method = null, params object[] args)
|
||||||
{
|
{
|
||||||
if (!subscribers.ContainsKey(eventLevel))
|
if (!subscribers.ContainsKey(eventLevel))
|
||||||
|
@ -105,6 +114,40 @@ namespace CapyKit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Emits an event with the given severity level, message, unique identifier, and method name one
|
||||||
|
/// time.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method is similar to <see cref="EmitEvent(EventLevel, string, string, string, object[])"/>
|
||||||
|
/// , but requires a unique identifier (such as a <see cref="Guid"/>) to prevent duplicate
|
||||||
|
/// emissions.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="eventLevel"> The severity level of the event. </param>
|
||||||
|
/// <param name="message">
|
||||||
|
/// The message describing the reason for the event. String formatting for <paramref name="args"/>
|
||||||
|
/// is accepted.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="uniqueIdentifier"> A unique identifier for the event emission. </param>
|
||||||
|
/// <param name="method">
|
||||||
|
/// (Optional) The name of the method where the event was raised.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="args">
|
||||||
|
/// A variable-length parameters list containing arguments for formatting the message.
|
||||||
|
/// </param>
|
||||||
|
/// <seealso cref="CallerMemberNameAttribute"/>
|
||||||
|
/// <seealso cref="Guid"/>
|
||||||
|
internal static void EmitEventOnce(EventLevel eventLevel, string message, string uniqueIdentifier, [CallerMemberName] string method = null, params object[] args)
|
||||||
|
{
|
||||||
|
if(uniqueIdentifiers.Contains(uniqueIdentifier))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uniqueIdentifiers.Add(uniqueIdentifier);
|
||||||
|
EmitEvent(eventLevel, message, method: method, args: args);
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,12 +205,19 @@ namespace CapyKit
|
||||||
public enum EventLevel
|
public enum EventLevel
|
||||||
{
|
{
|
||||||
/// <summary> Represents a critical error that requires immediate attention. </summary>
|
/// <summary> Represents a critical error that requires immediate attention. </summary>
|
||||||
|
[EnumerationDescription("Represents a critical error that requires immediate attention.")]
|
||||||
Critical = 0,
|
Critical = 0,
|
||||||
/// <summary> Represents an error that prevents the normal execution of the application. </summary>
|
/// <summary> Represents an error that prevents the normal execution of the application. </summary>
|
||||||
|
[EnumerationDescription("Represents an error that prevents the normal execution of the application.")]
|
||||||
Error = 1,
|
Error = 1,
|
||||||
|
/// <summary> Represents a warning indicating a non-critical issue that should be addressed. </summary>
|
||||||
|
[EnumerationDescription("Represents a warning indicating a non-critical issue that should be addressed.")]
|
||||||
|
Warning = 2,
|
||||||
/// <summary> Represents informational messages that provide useful context to the consumer. </summary>
|
/// <summary> Represents informational messages that provide useful context to the consumer. </summary>
|
||||||
Information = 2,
|
[EnumerationDescription("Represents informational messages that provide useful context to the consumer.")]
|
||||||
|
Information = 3,
|
||||||
/// <summary> Represents detailed messages that are typically used for debugging purposes. </summary>
|
/// <summary> Represents detailed messages that are typically used for debugging purposes. </summary>
|
||||||
Debug = 3
|
[EnumerationDescription("Represents detailed messages that are typically used for debugging purposes.")]
|
||||||
|
Debug = 4
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace CapyKit.Extensions
|
namespace CapyKit.Extensions
|
||||||
{
|
{
|
||||||
|
/// <summary> Provides static extentions methods for providing additional functionality for <see cref="Enum"/> types. </summary>
|
||||||
public static class EnumerationExtensions
|
public static class EnumerationExtensions
|
||||||
{
|
{
|
||||||
#region Methods
|
#region Methods
|
||||||
|
|
260
CapyKit/Extensions/LINQExtentions.cs
Normal file
260
CapyKit/Extensions/LINQExtentions.cs
Normal file
|
@ -0,0 +1,260 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CapyKit.Extensions
|
||||||
|
{
|
||||||
|
/// <summary> Provides static extension methods for performing common LINQ operations on <see cref="IEnumerable{T}"/> and <see cref="IQueryable{T}"/> collections. </summary>
|
||||||
|
public static class LINQExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Filters out items matching a <paramref name="predicate"/> from the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
||||||
|
/// <param name="source"> The source to act on. </param>
|
||||||
|
/// <param name="predicate"> The predicate. </param>
|
||||||
|
/// <returns>
|
||||||
|
/// An enumerator that allows foreach to be used to process remove in this collection.
|
||||||
|
/// </returns>
|
||||||
|
public static IEnumerable<T> Filter<T>(this IEnumerable<T> source, Func<T, bool> predicate)
|
||||||
|
{
|
||||||
|
return source.Where(item => !predicate(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Filters out items matching a <paramref name="predicate"/> from the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
||||||
|
/// <param name="source"> The source to act on. </param>
|
||||||
|
/// <param name="predicate"> The predicate. </param>
|
||||||
|
/// <returns>
|
||||||
|
/// An enumerator that allows foreach to be used to process remove in this collection.
|
||||||
|
/// </returns>
|
||||||
|
public static IQueryable<T> Filter<T>(this IQueryable<T> source, System.Linq.Expressions.Expression<Func<T, bool>> predicate)
|
||||||
|
{
|
||||||
|
if(predicate.Parameters.Count > 1)
|
||||||
|
{
|
||||||
|
CapyEventReporter.EmitEvent(EventLevel.Warning, "More than one parameter was found in the predicate, which could result in unexpected behavior.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var parameter = predicate.Parameters.FirstOrDefault();
|
||||||
|
var negatedPredicate = Expression.Not(predicate);
|
||||||
|
var lamda = Expression.Lambda<Func<T, bool>>(negatedPredicate, parameter);
|
||||||
|
|
||||||
|
return source.Where(lamda);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a page of items from a collection, skipping <paramref name="pageNumber"/> pages of
|
||||||
|
/// <paramref name="pageSize"/> items per page.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks> This method uses natural numbering starting at page 1. </remarks>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException">
|
||||||
|
/// Thrown when <paramref name="pageNumber"/> is less than <c>1</c> or if
|
||||||
|
/// <paramref name="pageSize"/> is less than
|
||||||
|
/// <c>1</c>.
|
||||||
|
/// </exception>
|
||||||
|
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
||||||
|
/// <param name="source"> The source to act on. </param>
|
||||||
|
/// <param name="pageNumber"> The page number to retrieve. </param>
|
||||||
|
/// <param name="pageSize"> Number of items per page. </param>
|
||||||
|
/// <returns>
|
||||||
|
/// An enumerator that allows foreach to be used to process page in this collection.
|
||||||
|
/// </returns>
|
||||||
|
public static IEnumerable<T> Page<T>(this IEnumerable<T> source, int pageNumber, int pageSize)
|
||||||
|
{
|
||||||
|
if (pageNumber < 1)
|
||||||
|
{
|
||||||
|
CapyEventReporter.EmitEvent(EventLevel.Error, "pageNumber is out of range. [{0}]", args: new[] { pageNumber });
|
||||||
|
throw new ArgumentOutOfRangeException("pageNumber");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pageSize < 1)
|
||||||
|
{
|
||||||
|
CapyEventReporter.EmitEvent(EventLevel.Error, "pageSize is out of range. [{0}]", args: new[] { pageSize });
|
||||||
|
throw new ArgumentOutOfRangeException("pageSize");
|
||||||
|
}
|
||||||
|
|
||||||
|
return source.Skip((pageNumber - 1) * pageSize).Take(pageSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a page of items from a collection, skipping <paramref name="pageNumber"/> pages of
|
||||||
|
/// <paramref name="pageSize"/> items per page.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks> This method uses natural numbering starting at page 1. </remarks>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException">
|
||||||
|
/// Thrown when <paramref name="pageNumber"/> is less than <c>1</c> or if
|
||||||
|
/// <paramref name="pageSize"/> is less than
|
||||||
|
/// <c>1</c>.
|
||||||
|
/// </exception>
|
||||||
|
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
||||||
|
/// <param name="source"> The source to act on. </param>
|
||||||
|
/// <param name="pageNumber"> The page number to retrieve. </param>
|
||||||
|
/// <param name="pageSize"> . </param>
|
||||||
|
/// <returns>
|
||||||
|
/// An enumerator that allows foreach to be used to process page in this collection.
|
||||||
|
/// </returns>
|
||||||
|
public static IQueryable<T> Page<T>(this IQueryable<T> source, int pageNumber, int pageSize)
|
||||||
|
{
|
||||||
|
if (pageNumber < 1)
|
||||||
|
{
|
||||||
|
CapyEventReporter.EmitEvent(EventLevel.Error, "pageNumber is out of range. [{0}]", args: new[] { pageNumber });
|
||||||
|
throw new ArgumentOutOfRangeException("pageNumber");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pageSize < 1)
|
||||||
|
{
|
||||||
|
CapyEventReporter.EmitEvent(EventLevel.Error, "pageSize is out of range. [{0}]", args: new[] { pageSize });
|
||||||
|
throw new ArgumentOutOfRangeException("pageSize");
|
||||||
|
}
|
||||||
|
|
||||||
|
return source.Skip((pageNumber - 1) * pageSize).Take(pageSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of pages of <paramref name="pageSize"/> size in the given collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException">
|
||||||
|
/// Thrown when <paramref name="pageSize"/> is less than <c>1</c>.
|
||||||
|
/// </exception>
|
||||||
|
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
||||||
|
/// <param name="source"> The source to act on. </param>
|
||||||
|
/// <param name="pageSize"> Size of the page. </param>
|
||||||
|
/// <returns> An int. </returns>
|
||||||
|
public static int PageCount<T>(this IEnumerable<T> source, int pageSize)
|
||||||
|
{
|
||||||
|
if (pageSize < 1)
|
||||||
|
{
|
||||||
|
CapyEventReporter.EmitEvent(EventLevel.Error, "pageSize is out of range. [{0}]", args: new[] { pageSize });
|
||||||
|
throw new ArgumentOutOfRangeException("pageSize");
|
||||||
|
}
|
||||||
|
|
||||||
|
var ceiling = Math.Ceiling(Convert.ToDouble(source.Count()) / pageSize);
|
||||||
|
return Convert.ToInt32(ceiling);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of pages of <paramref name="pageSize"/> size in the given collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException">
|
||||||
|
/// Thrown when <paramref name="pageSize"/> is less than <c>1</c>.
|
||||||
|
/// </exception>
|
||||||
|
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
||||||
|
/// <param name="source"> The source to act on. </param>
|
||||||
|
/// <param name="pageSize"> Size of the page. </param>
|
||||||
|
/// <returns> An int. </returns>
|
||||||
|
public static int PageCount<T>(this IQueryable<T> source, int pageSize)
|
||||||
|
{
|
||||||
|
if (pageSize < 1)
|
||||||
|
{
|
||||||
|
CapyEventReporter.EmitEvent(EventLevel.Error, "pageSize is out of range. [{0}]", args: new[] { pageSize });
|
||||||
|
throw new ArgumentOutOfRangeException("pageSize");
|
||||||
|
}
|
||||||
|
|
||||||
|
var ceiling = Math.Ceiling(Convert.ToDouble(source.Count()) / pageSize);
|
||||||
|
return Convert.ToInt32(ceiling);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> An IQueryable<T> extension method that left outer join. </summary>
|
||||||
|
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
||||||
|
/// <typeparam name="U"> Generic type parameter. </typeparam>
|
||||||
|
/// <typeparam name="TKey"> Type of the key. </typeparam>
|
||||||
|
/// <typeparam name="R"> Type of the r. </typeparam>
|
||||||
|
/// <param name="source"> The source to act on. </param>
|
||||||
|
/// <param name="inner"> The inner. </param>
|
||||||
|
/// <param name="outerSelector"> The outer selector. </param>
|
||||||
|
/// <param name="innerSelector"> The inner selector. </param>
|
||||||
|
/// <param name="resultSelector"> The result selector. </param>
|
||||||
|
/// <param name="defaultGenerator"> (Optional) The default generator. </param>
|
||||||
|
/// <returns> An IQueryable<R> </returns>
|
||||||
|
public static IQueryable<R> LeftOuterJoin<T, U, TKey, R>(this IQueryable<T> source, IQueryable<U> inner, Expression<Func<T, TKey>> outerSelector, Expression<Func<U, TKey>> innerSelector, Func<T, IEnumerable<U>, R> resultSelector, Func<T, U> defaultGenerator = null)
|
||||||
|
{
|
||||||
|
Func<T, IEnumerable<U>, R> resultOrDefaultSelector = (i, o) =>
|
||||||
|
{
|
||||||
|
if (defaultGenerator == null)
|
||||||
|
{
|
||||||
|
defaultGenerator = (t) => default(U);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!o.Any())
|
||||||
|
{
|
||||||
|
return resultSelector(i, new[] { defaultGenerator(i) });
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultSelector(i, o);
|
||||||
|
};
|
||||||
|
|
||||||
|
return source.LeftOuterJoin(inner, outerSelector, innerSelector, (a, b) => resultSelector(a, b));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> An IQueryable<T> extension method that left outer join. </summary>
|
||||||
|
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
||||||
|
/// <typeparam name="U"> Generic type parameter. </typeparam>
|
||||||
|
/// <typeparam name="TKey"> Type of the key. </typeparam>
|
||||||
|
/// <typeparam name="R"> Type of the r. </typeparam>
|
||||||
|
/// <param name="source"> The source to act on. </param>
|
||||||
|
/// <param name="inner"> The inner. </param>
|
||||||
|
/// <param name="outerSelector"> The outer selector. </param>
|
||||||
|
/// <param name="innerSelector"> The inner selector. </param>
|
||||||
|
/// <param name="resultSelector"> The result selector. </param>
|
||||||
|
/// <returns> An IQueryable<R> </returns>
|
||||||
|
private static IQueryable<R> LeftOuterJoin<T, U, TKey, R>(this IQueryable<T> source, IQueryable<U> inner, Expression<Func<T, TKey>> outerSelector, Expression<Func<U, TKey>> innerSelector, Expression<Func<T, IEnumerable<U>, R>> resultSelector)
|
||||||
|
{
|
||||||
|
return source.GroupJoin(inner, outerSelector, innerSelector, resultSelector);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> An IEnumable<T> extension method that left outer join. </summary>
|
||||||
|
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
||||||
|
/// <typeparam name="U"> Generic type parameter. </typeparam>
|
||||||
|
/// <typeparam name="TKey"> Type of the key. </typeparam>
|
||||||
|
/// <typeparam name="R"> Type of the r. </typeparam>
|
||||||
|
/// <param name="source"> The source to act on. </param>
|
||||||
|
/// <param name="inner"> The inner. </param>
|
||||||
|
/// <param name="outerSelector"> The outer selector. </param>
|
||||||
|
/// <param name="innerSelector"> The inner selector. </param>
|
||||||
|
/// <param name="resultSelector"> The result selector. </param>
|
||||||
|
/// <param name="defaultGenerator"> (Optional) The default generator. </param>
|
||||||
|
/// <returns>
|
||||||
|
/// An enumerator that allows foreach to be used to process left outter join in this collection.
|
||||||
|
/// </returns>
|
||||||
|
public static IEnumerable<R> LeftOuterJoin<T, U, TKey, R>(this IEnumerable<T> source, IEnumerable<U> inner, Func<T, TKey> outerSelector, Func<U, TKey> innerSelector, Func<T, IEnumerable<U>, R> resultSelector, Func<T, U> defaultGenerator = null)
|
||||||
|
{
|
||||||
|
var combined = source.GroupJoin(inner, outerSelector, innerSelector, (i, o) => new { inner = i, outer = o });
|
||||||
|
|
||||||
|
if (defaultGenerator == null)
|
||||||
|
{
|
||||||
|
defaultGenerator = (t) => default(U);
|
||||||
|
}
|
||||||
|
|
||||||
|
return combined.Select(anon =>
|
||||||
|
{
|
||||||
|
if (!anon.outer.Any())
|
||||||
|
{
|
||||||
|
return resultSelector(anon.inner, new[] { defaultGenerator(anon.inner) });
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultSelector(anon.inner, anon.outer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enumerates distinct items in this collection as defined by the key <paramref name="property"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"> Generic type parameter of the parent object. </typeparam>
|
||||||
|
/// <typeparam name="U"> Generic type parameter property value. </typeparam>
|
||||||
|
/// <param name="items"> The items to act on. </param>
|
||||||
|
/// <param name="property"> The property. </param>
|
||||||
|
/// <returns>
|
||||||
|
/// An enumerator that allows foreach to be used to process distinct items in this collection.
|
||||||
|
/// </returns>
|
||||||
|
public static IEnumerable<T> Distinct<T, U>(this IEnumerable<T> items, Func<T, U> property)
|
||||||
|
{
|
||||||
|
var propertyComparer = new PropertyComparer<T, U>(property);
|
||||||
|
return items.Distinct(propertyComparer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
56
CapyKit/Extensions/StringExtensions.cs
Normal file
56
CapyKit/Extensions/StringExtensions.cs
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CapyKit.Extensions
|
||||||
|
{
|
||||||
|
/// <summary> Provides static extentions methods for providing additional functionality for <see cref="string"/> types. </summary>
|
||||||
|
public static class StringExtensions
|
||||||
|
{
|
||||||
|
#region Members
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
#endregion Members
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
/// <summary> Replaces a null or empty string with a specified replacement string. </summary>
|
||||||
|
/// <param name="value"> The original string. </param>
|
||||||
|
/// <param name="replacement"> The replacement string. </param>
|
||||||
|
/// <returns>
|
||||||
|
/// The original string if not null or empty, otherwise the replacement string.
|
||||||
|
/// </returns>
|
||||||
|
/// <seealso cref="string.IsNullOrEmpty(string?)"/>
|
||||||
|
public static string IfNullOrEmpty(this string value, string replacement)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(value))
|
||||||
|
{
|
||||||
|
return replacement;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Replaces a null or whitespace string with a specified replacement string. </summary>
|
||||||
|
/// <param name="value"> The original string. </param>
|
||||||
|
/// <param name="replacement"> The replacement string. </param>
|
||||||
|
/// <returns>
|
||||||
|
/// The original string if not null or whitespace, otherwise the replacement string.
|
||||||
|
/// </returns>
|
||||||
|
/// <seealso cref="string.IsNullOrWhiteSpace(string?)"/>
|
||||||
|
public static string IfNullOrWhiteSpace(this string value, string replacement)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
return replacement;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Methods
|
||||||
|
}
|
||||||
|
}
|
106
CapyKit/Helpers/CompressionHelper.cs
Normal file
106
CapyKit/Helpers/CompressionHelper.cs
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO.Compression;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CapyKit.Helpers
|
||||||
|
{
|
||||||
|
/// <summary> A class that contains methods for managing data compression. </summary>
|
||||||
|
public static class CompressionHelper
|
||||||
|
{
|
||||||
|
#region Members
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
#endregion Members
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
/// <summary> Compresses a given object using the <c>gzip</c> algorithm. </summary>
|
||||||
|
/// <param name="obj"> The object. </param>
|
||||||
|
/// <returns> A byte array representing the compressed object in <c>gzip</c> format. </returns>
|
||||||
|
public static byte[] Compress(object obj)
|
||||||
|
{
|
||||||
|
var bytes = SerializationHelper.SerializeToBytes(obj);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var inputStream = new MemoryStream(bytes))
|
||||||
|
using (var outputStream = new MemoryStream())
|
||||||
|
{
|
||||||
|
using (var gzipStream = new GZipStream(outputStream, CompressionMode.Compress))
|
||||||
|
{
|
||||||
|
inputStream.Position = 0;
|
||||||
|
inputStream.CopyTo(gzipStream);
|
||||||
|
gzipStream.Flush();
|
||||||
|
}
|
||||||
|
return outputStream.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
CapyEventReporter.EmitEvent(EventLevel.Error, "Could not compress the object.");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Compresses a given object to a string using <c>base64</c> encoding of <c>gzip</c> format. </summary>
|
||||||
|
/// <param name="obj"> The object. </param>
|
||||||
|
/// <returns> A string in <c>base64</c> encoding. </returns>
|
||||||
|
public static string CompressToString(object obj)
|
||||||
|
{
|
||||||
|
var bytes = Compress(obj);
|
||||||
|
return Convert.ToBase64String(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Decompresses a given <c>base64</c> encoded string of <c>gzip</c> format. </summary>
|
||||||
|
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
||||||
|
/// <param name="encodedString"> The <c>base64</c> encoded <c>gzip</c> string. </param>
|
||||||
|
/// <returns> A <typeparamref name="T"/> typed object. </returns>
|
||||||
|
public static T Decompress<T>(string encodedString)
|
||||||
|
{
|
||||||
|
var bytes = Convert.FromBase64String(encodedString);
|
||||||
|
return Decompress<T>(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Decompresses a given compressed <c>gzip</c> byte stream. </summary>
|
||||||
|
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
||||||
|
/// <param name="byteStream"> The compressed byte stream. </param>
|
||||||
|
/// <returns> A <typeparamref name="T"/> typed object. </returns>
|
||||||
|
public static T Decompress<T>(byte[] byteStream)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var inputStream = new MemoryStream(byteStream))
|
||||||
|
using (var outputStream = new MemoryStream())
|
||||||
|
{
|
||||||
|
using (var gzipStream = new GZipStream(inputStream, CompressionMode.Decompress))
|
||||||
|
{
|
||||||
|
gzipStream.CopyTo(outputStream);
|
||||||
|
}
|
||||||
|
var bytes = outputStream.ToArray();
|
||||||
|
return SerializationHelper.Deserialize<T>(bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
CapyEventReporter.EmitEvent(EventLevel.Error, "Could not decompress the deflated object.");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Decompresses the given <c>base64</c> string in <c>gzip</c> format. </summary>
|
||||||
|
/// <param name="compressed"> The compressed string. </param>
|
||||||
|
/// <returns> A decomressed string. </returns>
|
||||||
|
public static string DecompressToString(string compressed)
|
||||||
|
{
|
||||||
|
var bytes = Convert.FromBase64String(compressed);
|
||||||
|
|
||||||
|
return Decompress<string>(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Methods
|
||||||
|
}
|
||||||
|
}
|
105
CapyKit/Helpers/SerializationHelper.cs
Normal file
105
CapyKit/Helpers/SerializationHelper.cs
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
using System.Runtime.Serialization.Formatters.Binary;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CapyKit.Helpers
|
||||||
|
{
|
||||||
|
public static class SerializationHelper
|
||||||
|
{
|
||||||
|
#region Members
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
#endregion Members
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
/// <summary> Serializes an object to a byte array. </summary>
|
||||||
|
/// <param name="obj"> The object. </param>
|
||||||
|
/// <returns> A <c>JSON</c> encoded string. </returns>
|
||||||
|
public static byte[] SerializeToBytes(object obj)
|
||||||
|
{
|
||||||
|
return JsonSerializer.SerializeToUtf8Bytes(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Serializes an object to a <c>JSON</c> encoded string. </summary>
|
||||||
|
/// <param name="obj"> The object. </param>
|
||||||
|
/// <returns> A <c>JSON</c> encoded string. </returns>
|
||||||
|
public static string SerializeToString(object obj)
|
||||||
|
{
|
||||||
|
return JsonSerializer.Serialize(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Deserializes an object to a given <typeparamref name="T"/> type. </summary>
|
||||||
|
/// <exception cref="FormatException"> Thrown when the format of the byte array is incorrect. </exception>
|
||||||
|
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
||||||
|
/// <param name="bytes"> The byte array representing a <typeparamref name="T"/> object. </param>
|
||||||
|
/// <returns> A <typeparamref name="T"/> object. </returns>
|
||||||
|
public static T Deserialize<T>(byte[] bytes)
|
||||||
|
{
|
||||||
|
var stream = new MemoryStream(bytes);
|
||||||
|
|
||||||
|
return Deserialize<T>(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Deserializes an object to a given <typeparamref name="T"/> type. </summary>
|
||||||
|
/// <exception cref="FormatException">
|
||||||
|
/// Thrown when the format of an input is incorrect.
|
||||||
|
/// </exception>
|
||||||
|
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
||||||
|
/// <param name="stream"> The steam. </param>
|
||||||
|
/// <returns> A <typeparamref name="T"/> object. </returns>
|
||||||
|
public static T Deserialize<T>(Stream stream)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var obj = JsonSerializer.Deserialize<T>(stream);
|
||||||
|
|
||||||
|
if(obj == null)
|
||||||
|
{
|
||||||
|
CapyEventReporter.EmitEvent(EventLevel.Error, "The deserialized object was null.");
|
||||||
|
throw new ArgumentNullException(nameof(stream));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (T)obj;
|
||||||
|
}
|
||||||
|
catch (JsonException ex)
|
||||||
|
{
|
||||||
|
CapyEventReporter.EmitEvent(EventLevel.Error, "JSON formatting error detected during deserialization of byte array for {0}.", args: new[] { typeof(T).Name });
|
||||||
|
throw new FormatException(string.Format("JSON formatting error detected during deserialization of byte array for {0}.", typeof(T).Name), ex);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
CapyEventReporter.EmitEvent(EventLevel.Error, "Could not deserialize object of type {0}.", args: new[] { typeof(T).Name });
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Deserializes a <c>JSON</c> encoded string to the given <typeparamref name="T"/>. </summary>
|
||||||
|
/// <typeparam name="T"> Generic type parameter. </typeparam>
|
||||||
|
/// <param name="str"> The <c>JSON</c> encoded string representing a <typeparamref name="T"/> object. </param>
|
||||||
|
/// <returns> A <typeparamref name="T"/> object. </returns>
|
||||||
|
public static T Deserialize<T>(string str)
|
||||||
|
{
|
||||||
|
if (typeof(T) == typeof(string))
|
||||||
|
{
|
||||||
|
return (T)Convert.ChangeType(str, typeof(T));
|
||||||
|
}
|
||||||
|
else if(string.IsNullOrWhiteSpace(str))
|
||||||
|
{
|
||||||
|
CapyEventReporter.EmitEvent(EventLevel.Error, "Could not deserialize an empty string.");
|
||||||
|
return default(T);
|
||||||
|
}
|
||||||
|
|
||||||
|
return JsonSerializer.Deserialize<T>(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Methods
|
||||||
|
}
|
||||||
|
}
|
101
CapyKit/PropertyComparer.cs
Normal file
101
CapyKit/PropertyComparer.cs
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CapyKit
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A object comparer that can accept a lambda expression to compare properties.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"> Generic type parameter of the parent object. </typeparam>
|
||||||
|
/// <typeparam name="U"> Generic type parameter of the property value. </typeparam>
|
||||||
|
/// <example>
|
||||||
|
/// using System;
|
||||||
|
/// using System.Collections.Generic;
|
||||||
|
/// using System.Linq;
|
||||||
|
///
|
||||||
|
/// class Program
|
||||||
|
/// {
|
||||||
|
/// static void Main(string[] args)
|
||||||
|
/// {
|
||||||
|
/// var people = new List<Person>
|
||||||
|
/// {
|
||||||
|
/// new Person { Name = "Alice", Age = 30 }, new Person { Name = "Bob", Age = 30 }, new
|
||||||
|
/// Person { Name = "Charlie", Age = 35 }
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// var comparer = new PropertyComparer<Person, int>(p => p.Age);
|
||||||
|
/// var distinctPeople = people.Distinct(comparer).ToList();
|
||||||
|
///
|
||||||
|
/// foreach (var person in distinctPeople)
|
||||||
|
/// {
|
||||||
|
/// Console.WriteLine($"{person.Name} - {person.Age}");
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// class Person
|
||||||
|
/// {
|
||||||
|
/// public string Name { get; set; }
|
||||||
|
/// public int Age { get; set; }
|
||||||
|
/// }
|
||||||
|
/// </example>
|
||||||
|
public class PropertyComparer<T, U> : IEqualityComparer<T>
|
||||||
|
{
|
||||||
|
/// <summary> The expression to retrieve the property. </summary>
|
||||||
|
private Func<T, U> expression;
|
||||||
|
|
||||||
|
/// <summary> Constructor. </summary>
|
||||||
|
/// <param name="expression"> The expression. </param>
|
||||||
|
public PropertyComparer(Func<T, U> expression)
|
||||||
|
{
|
||||||
|
this.expression = expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Determines whether the specified properties are equal. </summary>
|
||||||
|
/// <param name="x"> The first object of type <typeparamref name="T"/> to compare. </param>
|
||||||
|
/// <param name="y"> The second object of type <typeparamref name="T" /> to compare. </param>
|
||||||
|
/// <returns>
|
||||||
|
/// <see langword="true" /> if the specified objects are equal; otherwise,
|
||||||
|
/// <see langword="false" />.
|
||||||
|
/// </returns>
|
||||||
|
public bool Equals(T x, T y)
|
||||||
|
{
|
||||||
|
var left = expression.Invoke(x);
|
||||||
|
var right = expression.Invoke(y);
|
||||||
|
|
||||||
|
if (left == null && right == null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (left == null ^ right == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return left.Equals(right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Returns a hash code for the specified object. </summary>
|
||||||
|
/// <param name="obj">
|
||||||
|
/// The <see cref="T:System.Object" /> for which a hash code is to be returned.
|
||||||
|
/// </param>
|
||||||
|
/// <returns> A hash code for the specified object. </returns>
|
||||||
|
///
|
||||||
|
/// ### <exception cref="T:System.ArgumentNullException">
|
||||||
|
/// The type of <paramref name="obj" /> is a reference type and
|
||||||
|
/// <paramref name="obj" /> is
|
||||||
|
/// <see langword="null" />.
|
||||||
|
/// </exception>
|
||||||
|
public int GetHashCode(T obj)
|
||||||
|
{
|
||||||
|
var property = expression(obj);
|
||||||
|
|
||||||
|
return (property == null) ? 0 : property.GetHashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue