diff --git a/Backup.AWS/AmazonExtensions.cs b/Backup.AWS/AmazonExtensions.cs index 1a47c1a6..11fc4eb9 100644 --- a/Backup.AWS/AmazonExtensions.cs +++ b/Backup.AWS/AmazonExtensions.cs @@ -1,57 +1,56 @@ -namespace Ecng.Backup.Amazon -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Reflection; +namespace Ecng.Backup.Amazon; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +using global::Amazon; - using global::Amazon; +using Ecng.Common; - using Ecng.Common; +/// +/// Extension class for AWS. +/// +[CLSCompliant(false)] +public static class AmazonExtensions +{ + private static RegionEndpoint[] _endpoints; /// - /// Extension class for AWS. + /// All regions. /// - [CLSCompliant(false)] - public static class AmazonExtensions + public static IEnumerable Endpoints { - private static RegionEndpoint[] _endpoints; - - /// - /// All regions. - /// - public static IEnumerable Endpoints + get { - get + lock (typeof(AmazonExtensions)) { - lock (typeof(AmazonExtensions)) - { - _endpoints ??= [.. typeof(RegionEndpoint) - .GetFields(BindingFlags.Static | BindingFlags.Public) - .Where(f => f.FieldType == typeof(RegionEndpoint)) - .Select(f => (RegionEndpoint)f.GetValue(null))]; - } - - return _endpoints; + _endpoints ??= [.. typeof(RegionEndpoint) + .GetFields(BindingFlags.Static | BindingFlags.Public) + .Where(f => f.FieldType == typeof(RegionEndpoint)) + .Select(f => (RegionEndpoint)f.GetValue(null))]; } + + return _endpoints; } + } - /// - /// Get region by name. - /// - /// Region name. - /// Region. - public static RegionEndpoint GetEndpoint(string name) - { - if (name.IsEmpty()) - throw new ArgumentNullException(nameof(name)); + /// + /// Get region by name. + /// + /// Region name. + /// Region. + public static RegionEndpoint GetEndpoint(string name) + { + if (name.IsEmpty()) + throw new ArgumentNullException(nameof(name)); - var region = Endpoints.FirstOrDefault(e => - e.SystemName.EqualsIgnoreCase(name) || - e.SystemName.Remove("-").EqualsIgnoreCase(name) || - e.DisplayName.EqualsIgnoreCase(name)); + var region = Endpoints.FirstOrDefault(e => + e.SystemName.EqualsIgnoreCase(name) || + e.SystemName.Remove("-").EqualsIgnoreCase(name) || + e.DisplayName.EqualsIgnoreCase(name)); - return region ?? RegionEndpoint.GetBySystemName(name); - } + return region ?? RegionEndpoint.GetBySystemName(name); } } \ No newline at end of file diff --git a/Backup.AWS/AmazonGlacierService.cs b/Backup.AWS/AmazonGlacierService.cs index 85460def..7ee148b6 100644 --- a/Backup.AWS/AmazonGlacierService.cs +++ b/Backup.AWS/AmazonGlacierService.cs @@ -1,144 +1,143 @@ -namespace Ecng.Backup.Amazon -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Threading; - using System.Threading.Tasks; +namespace Ecng.Backup.Amazon; + +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; - using global::Amazon; - using global::Amazon.Runtime; - using global::Amazon.Glacier.Model; - using global::Amazon.Glacier; +using global::Amazon; +using global::Amazon.Runtime; +using global::Amazon.Glacier.Model; +using global::Amazon.Glacier; - using Ecng.Common; +using Ecng.Common; + +/// +/// The data storage service based on Amazon Glacier https://aws.amazon.com/s3/glacier/ . +/// +public class AmazonGlacierService : Disposable, IBackupService +{ + private readonly IAmazonGlacier _client; + private readonly string _vaultName; + private readonly AWSCredentials _credentials; + private readonly RegionEndpoint _endpoint; + private const int _bufferSize = FileSizes.MB * 100; /// - /// The data storage service based on Amazon Glacier https://aws.amazon.com/s3/glacier/ . + /// Initializes a new instance of the . /// - public class AmazonGlacierService : Disposable, IBackupService + /// Region address. + /// Storage name. + /// Key. + /// Secret. + public AmazonGlacierService(string endpoint, string bucket, string accessKey, string secretKey) + : this(AmazonExtensions.GetEndpoint(endpoint), bucket, accessKey, secretKey) { - private readonly IAmazonGlacier _client; - private readonly string _vaultName; - private readonly AWSCredentials _credentials; - private readonly RegionEndpoint _endpoint; - private const int _bufferSize = FileSizes.MB * 100; - - /// - /// Initializes a new instance of the . - /// - /// Region address. - /// Storage name. - /// Key. - /// Secret. - public AmazonGlacierService(string endpoint, string bucket, string accessKey, string secretKey) - : this(AmazonExtensions.GetEndpoint(endpoint), bucket, accessKey, secretKey) - { - } + } - /// - /// Initializes a new instance of the . - /// - /// Region address. - /// Storage name. - /// Key. - /// Secret. - [CLSCompliant(false)] - public AmazonGlacierService(RegionEndpoint endpoint, string vaultName, string accessKey, string secretKey) - { - _credentials = new BasicAWSCredentials(accessKey, secretKey); - _endpoint = endpoint ?? throw new ArgumentNullException(nameof(endpoint)); - _vaultName = vaultName.ThrowIfEmpty(nameof(vaultName)); - _client = new AmazonGlacierClient(_credentials, _endpoint); - } + /// + /// Initializes a new instance of the . + /// + /// Region address. + /// Storage name. + /// Key. + /// Secret. + [CLSCompliant(false)] + public AmazonGlacierService(RegionEndpoint endpoint, string vaultName, string accessKey, string secretKey) + { + _credentials = new BasicAWSCredentials(accessKey, secretKey); + _endpoint = endpoint ?? throw new ArgumentNullException(nameof(endpoint)); + _vaultName = vaultName.ThrowIfEmpty(nameof(vaultName)); + _client = new AmazonGlacierClient(_credentials, _endpoint); + } - bool IBackupService.CanFolders => false; - bool IBackupService.CanPublish => false; - bool IBackupService.CanPartialDownload => true; + bool IBackupService.CanFolders => false; + bool IBackupService.CanPublish => false; + bool IBackupService.CanPartialDownload => true; - IAsyncEnumerable IBackupService.FindAsync(BackupEntry parent, string criteria, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } + IAsyncEnumerable IBackupService.FindAsync(BackupEntry parent, string criteria, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } - Task IBackupService.DeleteAsync(BackupEntry entry, CancellationToken cancellationToken) - => _client.DeleteArchiveAsync(new() - { - VaultName = _vaultName, - ArchiveId = entry.Name, - }, cancellationToken); + Task IBackupService.DeleteAsync(BackupEntry entry, CancellationToken cancellationToken) + => _client.DeleteArchiveAsync(new() + { + VaultName = _vaultName, + ArchiveId = entry.Name, + }, cancellationToken); - Task IBackupService.FillInfoAsync(BackupEntry entry, CancellationToken cancellationToken) - => throw new NotImplementedException(); + Task IBackupService.FillInfoAsync(BackupEntry entry, CancellationToken cancellationToken) + => throw new NotImplementedException(); - async Task IBackupService.DownloadAsync(BackupEntry entry, Stream stream, long? offset, long? length, Action progress, CancellationToken cancellationToken) + async Task IBackupService.DownloadAsync(BackupEntry entry, Stream stream, long? offset, long? length, Action progress, CancellationToken cancellationToken) + { + GetJobOutputRequest request = new() { - GetJobOutputRequest request = new() - { - //JobId = jobId, - VaultName = _vaultName, - }; + //JobId = jobId, + VaultName = _vaultName, + }; - if (offset != null || length != null) - { - if (offset is null || length is null) - throw new NotSupportedException(); + if (offset != null || length != null) + { + if (offset is null || length is null) + throw new NotSupportedException(); - request.Range = $"bytes={offset}-{offset + length}"; - } + request.Range = $"bytes={offset}-{offset + length}"; + } - var response = await _client.GetJobOutputAsync(request, cancellationToken); + var response = await _client.GetJobOutputAsync(request, cancellationToken); - using var webStream = response.Body; + using var webStream = response.Body; - var bytes = new byte[_bufferSize]; - var readTotal = 0L; + var bytes = new byte[_bufferSize]; + var readTotal = 0L; - var prevProgress = -1; + var prevProgress = -1; - var objLen = 0L; //TODO + var objLen = 0L; //TODO - while (readTotal < objLen) - { - var expected = (int)(objLen - readTotal).Min(_bufferSize); - var actual = await webStream.ReadAsync(bytes, 0, expected, cancellationToken); + while (readTotal < objLen) + { + var expected = (int)(objLen - readTotal).Min(_bufferSize); + var actual = await webStream.ReadAsync(bytes, 0, expected, cancellationToken); - if (actual == 0) - break; + if (actual == 0) + break; - await stream.WriteAsync(bytes, 0, actual, cancellationToken); + await stream.WriteAsync(bytes, 0, actual, cancellationToken); - readTotal += actual; + readTotal += actual; - var currProgress = (int)(readTotal * 100L / objLen); + var currProgress = (int)(readTotal * 100L / objLen); - if (currProgress < 100 && prevProgress < currProgress) - { - progress(currProgress); - prevProgress = currProgress; - } + if (currProgress < 100 && prevProgress < currProgress) + { + progress(currProgress); + prevProgress = currProgress; } } + } - Task IBackupService.UploadAsync(BackupEntry entry, Stream stream, Action progress, CancellationToken cancellationToken) - => throw new NotImplementedException(); + Task IBackupService.UploadAsync(BackupEntry entry, Stream stream, Action progress, CancellationToken cancellationToken) + => throw new NotImplementedException(); - Task IBackupService.PublishAsync(BackupEntry entry, CancellationToken cancellationToken) - => throw new NotSupportedException(); + Task IBackupService.PublishAsync(BackupEntry entry, CancellationToken cancellationToken) + => throw new NotSupportedException(); - Task IBackupService.UnPublishAsync(BackupEntry entry, CancellationToken cancellationToken) - => throw new NotSupportedException(); + Task IBackupService.UnPublishAsync(BackupEntry entry, CancellationToken cancellationToken) + => throw new NotSupportedException(); - Task IBackupService.CreateFolder(BackupEntry entry, CancellationToken cancellationToken) - => throw new NotSupportedException(); + Task IBackupService.CreateFolder(BackupEntry entry, CancellationToken cancellationToken) + => throw new NotSupportedException(); - /// - /// Disposes the managed resources. - /// - protected override void DisposeManaged() - { - _client.Dispose(); - base.DisposeManaged(); - } + /// + /// Disposes the managed resources. + /// + protected override void DisposeManaged() + { + _client.Dispose(); + base.DisposeManaged(); } } \ No newline at end of file diff --git a/Backup.AWS/AmazonS3Service.cs b/Backup.AWS/AmazonS3Service.cs index 84f2a7ba..da8377b3 100644 --- a/Backup.AWS/AmazonS3Service.cs +++ b/Backup.AWS/AmazonS3Service.cs @@ -1,307 +1,306 @@ -namespace Ecng.Backup.Amazon +namespace Ecng.Backup.Amazon; + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Net; +using System.Runtime.CompilerServices; + +using global::Amazon; +using global::Amazon.Runtime; +using global::Amazon.S3; +using global::Amazon.S3.Model; + +using Ecng.Common; + +/// +/// The data storage service based on Amazon S3 https://aws.amazon.com/s3/ . +/// +public class AmazonS3Service : Disposable, IBackupService { - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Threading; - using System.Threading.Tasks; - using System.Net; - using System.Runtime.CompilerServices; - - using global::Amazon; - using global::Amazon.Runtime; - using global::Amazon.S3; - using global::Amazon.S3.Model; - - using Ecng.Common; + private readonly string _bucket; + private readonly IAmazonS3 _client; + private const int _bufferSize = FileSizes.MB * 10; + private readonly AWSCredentials _credentials; + private readonly RegionEndpoint _endpoint; /// - /// The data storage service based on Amazon S3 https://aws.amazon.com/s3/ . + /// Initializes a new instance of the . /// - public class AmazonS3Service : Disposable, IBackupService + /// Region address. + /// Storage name. + /// Key. + /// Secret. + public AmazonS3Service(string endpoint, string bucket, string accessKey, string secretKey) + : this(AmazonExtensions.GetEndpoint(endpoint), bucket, accessKey, secretKey) { - private readonly string _bucket; - private readonly IAmazonS3 _client; - private const int _bufferSize = FileSizes.MB * 10; - private readonly AWSCredentials _credentials; - private readonly RegionEndpoint _endpoint; - - /// - /// Initializes a new instance of the . - /// - /// Region address. - /// Storage name. - /// Key. - /// Secret. - public AmazonS3Service(string endpoint, string bucket, string accessKey, string secretKey) - : this(AmazonExtensions.GetEndpoint(endpoint), bucket, accessKey, secretKey) - { - } + } - /// - /// Initializes a new instance of the . - /// - /// Region address. - /// Storage name. - /// Key. - /// Secret. - [CLSCompliant(false)] - public AmazonS3Service(RegionEndpoint endpoint, string bucket, string accessKey, string secretKey) - { - _credentials = new BasicAWSCredentials(accessKey, secretKey); - _endpoint = endpoint ?? throw new ArgumentNullException(nameof(endpoint)); - _bucket = bucket.ThrowIfEmpty(nameof(bucket)); - _client = new AmazonS3Client(_credentials, _endpoint); - } + /// + /// Initializes a new instance of the . + /// + /// Region address. + /// Storage name. + /// Key. + /// Secret. + [CLSCompliant(false)] + public AmazonS3Service(RegionEndpoint endpoint, string bucket, string accessKey, string secretKey) + { + _credentials = new BasicAWSCredentials(accessKey, secretKey); + _endpoint = endpoint ?? throw new ArgumentNullException(nameof(endpoint)); + _bucket = bucket.ThrowIfEmpty(nameof(bucket)); + _client = new AmazonS3Client(_credentials, _endpoint); + } - bool IBackupService.CanFolders => false; - bool IBackupService.CanPublish => true; - bool IBackupService.CanPartialDownload => true; + bool IBackupService.CanFolders => false; + bool IBackupService.CanPublish => true; + bool IBackupService.CanPartialDownload => true; - async IAsyncEnumerable IBackupService.FindAsync(BackupEntry parent, string criteria, [EnumeratorCancellation]CancellationToken cancellationToken) + async IAsyncEnumerable IBackupService.FindAsync(BackupEntry parent, string criteria, [EnumeratorCancellation]CancellationToken cancellationToken) + { + //if (parent != null && !parent.IsDirectory) + // throw new ArgumentException("{0} should be directory.".Put(parent.Name), "parent"); + + var request = new ListObjectsV2Request { - //if (parent != null && !parent.IsDirectory) - // throw new ArgumentException("{0} should be directory.".Put(parent.Name), "parent"); + BucketName = _bucket, + Prefix = parent != null ? parent.GetFullPath() : null, + }; - var request = new ListObjectsV2Request - { - BucketName = _bucket, - Prefix = parent != null ? parent.GetFullPath() : null, - }; + if (!criteria.IsEmpty()) + request.Prefix += "/" + criteria; - if (!criteria.IsEmpty()) - request.Prefix += "/" + criteria; + do + { + var response = await _client.ListObjectsV2Async(request, cancellationToken); - do + foreach (var entry in response.S3Objects) { - var response = await _client.ListObjectsV2Async(request, cancellationToken); - - foreach (var entry in response.S3Objects) - { - var be = GetPath(entry.Key); - be.LastModified = entry.LastModified; - be.Size = entry.Size; - yield return be; - } + var be = GetPath(entry.Key); + be.LastModified = entry.LastModified; + be.Size = entry.Size; + yield return be; + } - foreach (var commonPrefix in response.CommonPrefixes) + foreach (var commonPrefix in response.CommonPrefixes) + { + yield return new() { - yield return new() - { - Name = commonPrefix, - Parent = parent, - }; - } - - if (response.IsTruncated) - request.ContinuationToken = response.NextContinuationToken; - else - break; + Name = commonPrefix, + Parent = parent, + }; } - while (true); + + if (response.IsTruncated) + request.ContinuationToken = response.NextContinuationToken; + else + break; } + while (true); + } - Task IBackupService.DeleteAsync(BackupEntry entry, CancellationToken cancellationToken) - => _client.DeleteObjectAsync(_bucket, entry.GetFullPath(), cancellationToken); + Task IBackupService.DeleteAsync(BackupEntry entry, CancellationToken cancellationToken) + => _client.DeleteObjectAsync(_bucket, entry.GetFullPath(), cancellationToken); - async Task IBackupService.DownloadAsync(BackupEntry entry, Stream stream, long? offset, long? length, Action progress, CancellationToken cancellationToken) - { - if (entry is null) - throw new ArgumentNullException(nameof(entry)); + async Task IBackupService.DownloadAsync(BackupEntry entry, Stream stream, long? offset, long? length, Action progress, CancellationToken cancellationToken) + { + if (entry is null) + throw new ArgumentNullException(nameof(entry)); - if (stream is null) - throw new ArgumentNullException(nameof(stream)); + if (stream is null) + throw new ArgumentNullException(nameof(stream)); - if (progress is null) - throw new ArgumentNullException(nameof(progress)); + if (progress is null) + throw new ArgumentNullException(nameof(progress)); - var key = entry.GetFullPath(); + var key = entry.GetFullPath(); - var request = new GetObjectRequest - { - BucketName = _bucket, - Key = key, - }; + var request = new GetObjectRequest + { + BucketName = _bucket, + Key = key, + }; - if (offset != null || length != null) - { - if (offset is null || length is null) - throw new NotSupportedException(); + if (offset != null || length != null) + { + if (offset is null || length is null) + throw new NotSupportedException(); - request.ByteRange = new(offset.Value, offset.Value + length.Value); - } + request.ByteRange = new(offset.Value, offset.Value + length.Value); + } - var bytes = new byte[_bufferSize]; - var readTotal = 0L; + var bytes = new byte[_bufferSize]; + var readTotal = 0L; - var prevProgress = -1; + var prevProgress = -1; - using (var response = await _client.GetObjectAsync(request, cancellationToken)) - using (var responseStream = response.ResponseStream) - { - var objLen = response.ContentLength; + using (var response = await _client.GetObjectAsync(request, cancellationToken)) + using (var responseStream = response.ResponseStream) + { + var objLen = response.ContentLength; - while (readTotal < objLen) - { - var expected = (int)(objLen - readTotal).Min(_bufferSize); - var actual = await responseStream.ReadAsync(bytes, 0, expected, cancellationToken); + while (readTotal < objLen) + { + var expected = (int)(objLen - readTotal).Min(_bufferSize); + var actual = await responseStream.ReadAsync(bytes, 0, expected, cancellationToken); - if (actual == 0) - break; + if (actual == 0) + break; - await stream.WriteAsync(bytes, 0, actual, cancellationToken); + await stream.WriteAsync(bytes, 0, actual, cancellationToken); - readTotal += actual; + readTotal += actual; - var currProgress = (int)(readTotal * 100L / objLen); + var currProgress = (int)(readTotal * 100L / objLen); - if (currProgress < 100 && prevProgress < currProgress) - { - progress(currProgress); - prevProgress = currProgress; - } + if (currProgress < 100 && prevProgress < currProgress) + { + progress(currProgress); + prevProgress = currProgress; } } - - if (prevProgress < 100) - progress(100); } - async Task IBackupService.UploadAsync(BackupEntry entry, Stream stream, Action progress, CancellationToken cancellationToken) - { - if (entry is null) - throw new ArgumentNullException(nameof(entry)); - - if (stream is null) - throw new ArgumentNullException(nameof(stream)); - - if (progress is null) - throw new ArgumentNullException(nameof(progress)); - - var key = entry.GetFullPath(); - - var initResponse = await _client.InitiateMultipartUploadAsync(new() - { - BucketName = _bucket, - Key = key, - }, cancellationToken); + if (prevProgress < 100) + progress(100); + } - var filePosition = 0L; - var prevProgress = -1; + async Task IBackupService.UploadAsync(BackupEntry entry, Stream stream, Action progress, CancellationToken cancellationToken) + { + if (entry is null) + throw new ArgumentNullException(nameof(entry)); - var etags = new List(); + if (stream is null) + throw new ArgumentNullException(nameof(stream)); - var partNum = 1; + if (progress is null) + throw new ArgumentNullException(nameof(progress)); - while (filePosition < stream.Length) - { - var response = await _client.UploadPartAsync(new() - { - BucketName = _bucket, - UploadId = initResponse.UploadId, - PartNumber = partNum, - PartSize = _bufferSize, - //FilePosition = filePosition, - InputStream = stream, - Key = key - }, cancellationToken); + var key = entry.GetFullPath(); - etags.Add(new(partNum, response.ETag)); + var initResponse = await _client.InitiateMultipartUploadAsync(new() + { + BucketName = _bucket, + Key = key, + }, cancellationToken); - filePosition += _bufferSize; + var filePosition = 0L; + var prevProgress = -1; - var currProgress = (int)(filePosition.Min(stream.Length) * 100 / stream.Length); + var etags = new List(); - if (currProgress > prevProgress) - { - progress(currProgress); - prevProgress = currProgress; - } + var partNum = 1; - partNum++; - } - - await _client.CompleteMultipartUploadAsync(new() + while (filePosition < stream.Length) + { + var response = await _client.UploadPartAsync(new() { BucketName = _bucket, UploadId = initResponse.UploadId, - Key = key, - PartETags = etags + PartNumber = partNum, + PartSize = _bufferSize, + //FilePosition = filePosition, + InputStream = stream, + Key = key }, cancellationToken); - if (prevProgress < 100) - progress(100); - } + etags.Add(new(partNum, response.ETag)); - Task IBackupService.CreateFolder(BackupEntry entry, CancellationToken cancellationToken) - => throw new NotSupportedException(); + filePosition += _bufferSize; - async Task IBackupService.FillInfoAsync(BackupEntry entry, CancellationToken cancellationToken) - { - var key = entry.GetFullPath(); + var currProgress = (int)(filePosition.Min(stream.Length) * 100 / stream.Length); - var response = await _client.GetObjectMetadataAsync(new() + if (currProgress > prevProgress) { - BucketName = _bucket, - Key = key, - }, cancellationToken); + progress(currProgress); + prevProgress = currProgress; + } - entry.Size = response.ContentLength; + partNum++; } - async Task IBackupService.PublishAsync(BackupEntry entry, CancellationToken cancellationToken) + await _client.CompleteMultipartUploadAsync(new() { - var key = entry.GetFullPath(); + BucketName = _bucket, + UploadId = initResponse.UploadId, + Key = key, + PartETags = etags + }, cancellationToken); + + if (prevProgress < 100) + progress(100); + } - var response = await _client.PutACLAsync(new() - { - BucketName = _bucket, - Key = key, - CannedACL = S3CannedACL.PublicRead, - }, cancellationToken); + Task IBackupService.CreateFolder(BackupEntry entry, CancellationToken cancellationToken) + => throw new NotSupportedException(); - if (response.HttpStatusCode != HttpStatusCode.OK) - throw new InvalidOperationException(response.HttpStatusCode.To()); - - return $"https://{_bucket}.s3.{_endpoint.SystemName}.amazonaws.com/{key}"; - } + async Task IBackupService.FillInfoAsync(BackupEntry entry, CancellationToken cancellationToken) + { + var key = entry.GetFullPath(); - async Task IBackupService.UnPublishAsync(BackupEntry entry, CancellationToken cancellationToken) + var response = await _client.GetObjectMetadataAsync(new() { - var key = entry.GetFullPath(); + BucketName = _bucket, + Key = key, + }, cancellationToken); - var response = await _client.PutACLAsync(new() - { - BucketName = _bucket, - Key = key, - CannedACL = S3CannedACL.Private, - }, cancellationToken); + entry.Size = response.ContentLength; + } - if (response.HttpStatusCode != HttpStatusCode.OK) - throw new InvalidOperationException(response.HttpStatusCode.To()); - } + async Task IBackupService.PublishAsync(BackupEntry entry, CancellationToken cancellationToken) + { + var key = entry.GetFullPath(); - private static BackupEntry GetPath(string key) + var response = await _client.PutACLAsync(new() { - var entities = key.Split('/').Select(p => new BackupEntry { Name = p }).ToArray(); + BucketName = _bucket, + Key = key, + CannedACL = S3CannedACL.PublicRead, + }, cancellationToken); + + if (response.HttpStatusCode != HttpStatusCode.OK) + throw new InvalidOperationException(response.HttpStatusCode.To()); + + return $"https://{_bucket}.s3.{_endpoint.SystemName}.amazonaws.com/{key}"; + } - BackupEntry parent = null; + async Task IBackupService.UnPublishAsync(BackupEntry entry, CancellationToken cancellationToken) + { + var key = entry.GetFullPath(); - foreach (var entity in entities) - { - entity.Parent = parent; - parent = entity; - } + var response = await _client.PutACLAsync(new() + { + BucketName = _bucket, + Key = key, + CannedACL = S3CannedACL.Private, + }, cancellationToken); - return entities.Last(); - } + if (response.HttpStatusCode != HttpStatusCode.OK) + throw new InvalidOperationException(response.HttpStatusCode.To()); + } + + private static BackupEntry GetPath(string key) + { + var entities = key.Split('/').Select(p => new BackupEntry { Name = p }).ToArray(); - /// - protected override void DisposeManaged() + BackupEntry parent = null; + + foreach (var entity in entities) { - _client.Dispose(); - base.DisposeManaged(); + entity.Parent = parent; + parent = entity; } + + return entities.Last(); + } + + /// + protected override void DisposeManaged() + { + _client.Dispose(); + base.DisposeManaged(); } } \ No newline at end of file diff --git a/Backup/IBackupService.cs b/Backup/IBackupService.cs index 05cb85b2..744cfc33 100644 --- a/Backup/IBackupService.cs +++ b/Backup/IBackupService.cs @@ -1,100 +1,99 @@ -namespace Ecng.Backup -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Threading; - using System.Threading.Tasks; +namespace Ecng.Backup; + +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +/// +/// The interface describing online data storage service. +/// +public interface IBackupService : IDisposable +{ /// - /// The interface describing online data storage service. + /// Is publishing feature available. /// - public interface IBackupService : IDisposable - { - /// - /// Is publishing feature available. - /// - bool CanPublish { get; } + bool CanPublish { get; } - /// - /// Is folders feature available. - /// - bool CanFolders { get; } + /// + /// Is folders feature available. + /// + bool CanFolders { get; } - /// - /// Is partial download feature available. - /// - bool CanPartialDownload { get; } + /// + /// Is partial download feature available. + /// + bool CanPartialDownload { get; } - /// - /// Is partial upload feature available. - /// - /// - /// - /// - Task CreateFolder(BackupEntry entry, CancellationToken cancellationToken = default); + /// + /// Is partial upload feature available. + /// + /// + /// + /// + Task CreateFolder(BackupEntry entry, CancellationToken cancellationToken = default); - /// - /// Find files by the specified criteria. - /// - /// Parent element. Can be null. - /// Criteria. - /// - /// File list. - IAsyncEnumerable FindAsync(BackupEntry parent, string criteria, CancellationToken cancellationToken = default); + /// + /// Find files by the specified criteria. + /// + /// Parent element. Can be null. + /// Criteria. + /// + /// File list. + IAsyncEnumerable FindAsync(BackupEntry parent, string criteria, CancellationToken cancellationToken = default); - /// - /// Fill file info. - /// - /// Element. - /// - /// - Task FillInfoAsync(BackupEntry entry, CancellationToken cancellationToken = default); + /// + /// Fill file info. + /// + /// Element. + /// + /// + Task FillInfoAsync(BackupEntry entry, CancellationToken cancellationToken = default); - /// - /// Delete file from the service. - /// - /// Element. - /// - /// - Task DeleteAsync(BackupEntry entry, CancellationToken cancellationToken = default); + /// + /// Delete file from the service. + /// + /// Element. + /// + /// + Task DeleteAsync(BackupEntry entry, CancellationToken cancellationToken = default); - /// - /// Save file. - /// - /// Element. - /// - /// - /// - /// Progress notification. - /// - /// - Task DownloadAsync(BackupEntry entry, Stream stream, long? offset, long? length, Action progress, CancellationToken cancellationToken = default); + /// + /// Save file. + /// + /// Element. + /// + /// + /// + /// Progress notification. + /// + /// + Task DownloadAsync(BackupEntry entry, Stream stream, long? offset, long? length, Action progress, CancellationToken cancellationToken = default); - /// - /// Upload file. - /// - /// Element. - /// The stream of the open file into which data from the service will be downloaded. - /// Progress notification. - /// - /// - Task UploadAsync(BackupEntry entry, Stream stream, Action progress, CancellationToken cancellationToken = default); + /// + /// Upload file. + /// + /// Element. + /// The stream of the open file into which data from the service will be downloaded. + /// Progress notification. + /// + /// + Task UploadAsync(BackupEntry entry, Stream stream, Action progress, CancellationToken cancellationToken = default); - /// - /// Get public url for the specified element. - /// - /// Element. - /// - /// Public url. - Task PublishAsync(BackupEntry entry, CancellationToken cancellationToken = default); + /// + /// Get public url for the specified element. + /// + /// Element. + /// + /// Public url. + Task PublishAsync(BackupEntry entry, CancellationToken cancellationToken = default); - /// - /// Remove public url for the specified element. - /// - /// Element. - /// - /// - Task UnPublishAsync(BackupEntry entry, CancellationToken cancellationToken = default); - } + /// + /// Remove public url for the specified element. + /// + /// Element. + /// + /// + Task UnPublishAsync(BackupEntry entry, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/Collections/BackwardComparer.cs b/Collections/BackwardComparer.cs index 2cfa5503..c9c82b6e 100644 --- a/Collections/BackwardComparer.cs +++ b/Collections/BackwardComparer.cs @@ -1,18 +1,17 @@ -namespace Ecng.Collections -{ - using System; - using System.Collections.Generic; +namespace Ecng.Collections; + +using System; +using System.Collections.Generic; - /// - /// Provides a comparer that compares objects in reverse order. - /// - /// The type of objects to compare. Must implement . - public class BackwardComparer : IComparer - where T : IComparable +/// +/// Provides a comparer that compares objects in reverse order. +/// +/// The type of objects to compare. Must implement . +public class BackwardComparer : IComparer + where T : IComparable +{ + int IComparer.Compare(T x, T y) { - int IComparer.Compare(T x, T y) - { - return -x.CompareTo(y); - } + return -x.CompareTo(y); } } \ No newline at end of file diff --git a/Collections/BaseBlockingQueue.cs b/Collections/BaseBlockingQueue.cs index e02b4bcc..3e23313f 100644 --- a/Collections/BaseBlockingQueue.cs +++ b/Collections/BaseBlockingQueue.cs @@ -1,324 +1,323 @@ -namespace Ecng.Collections +namespace Ecng.Collections; + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Threading; + +using Ecng.Common; + +// http://stackoverflow.com/questions/530211/creating-a-blocking-queuet-in-net +/// +/// Abstract base class for a blocking queue implementation with a generic type and an inner collection. +/// +/// The type of elements in the queue. +/// The type of the inner collection, which must implement . +public abstract class BaseBlockingQueue(TF innerCollection) : ISynchronizedCollection, IBlockingQueue + where TF : ICollection { - using System; - using System.Collections; - using System.Collections.Generic; - using System.Threading; + /// + /// Gets the inner collection used to store the queue elements. + /// + protected TF InnerCollection { get; } = innerCollection; - using Ecng.Common; + // -1 is unlimited + private int _maxSize = -1; - // http://stackoverflow.com/questions/530211/creating-a-blocking-queuet-in-net /// - /// Abstract base class for a blocking queue implementation with a generic type and an inner collection. + /// Gets or sets the maximum size of the queue. A value of -1 indicates no size limit. /// - /// The type of elements in the queue. - /// The type of the inner collection, which must implement . - public abstract class BaseBlockingQueue(TF innerCollection) : ISynchronizedCollection, IBlockingQueue - where TF : ICollection + /// Thrown when the value is 0 or less than -1. + public int MaxSize { - /// - /// Gets the inner collection used to store the queue elements. - /// - protected TF InnerCollection { get; } = innerCollection; - - // -1 is unlimited - private int _maxSize = -1; - - /// - /// Gets or sets the maximum size of the queue. A value of -1 indicates no size limit. - /// - /// Thrown when the value is 0 or less than -1. - public int MaxSize + get => _maxSize; + set { - get => _maxSize; - set - { - if (value == 0 || value < -1) - throw new ArgumentOutOfRangeException(nameof(value)); + if (value == 0 || value < -1) + throw new ArgumentOutOfRangeException(nameof(value)); - _maxSize = value; - } + _maxSize = value; } + } - /// - /// Gets the synchronization object used to coordinate access to the queue. - /// - public SyncObject SyncRoot { get; } = new(); + /// + /// Gets the synchronization object used to coordinate access to the queue. + /// + public SyncObject SyncRoot { get; } = new(); - /// - /// Gets the current number of items in the queue. - /// - public int Count => InnerCollection.Count; + /// + /// Gets the current number of items in the queue. + /// + public int Count => InnerCollection.Count; - private bool _isClosed; + private bool _isClosed; - /// - /// Gets a value indicating whether the queue is closed. - /// - public bool IsClosed => _isClosed; + /// + /// Gets a value indicating whether the queue is closed. + /// + public bool IsClosed => _isClosed; - /// - /// Closes the queue, preventing further enqueues and waking up any blocked threads. - /// - public void Close() + /// + /// Closes the queue, preventing further enqueues and waking up any blocked threads. + /// + public void Close() + { + lock (SyncRoot) { - lock (SyncRoot) - { - _isClosed = true; - Monitor.PulseAll(SyncRoot); - } + _isClosed = true; + Monitor.PulseAll(SyncRoot); } + } - /// - /// Reopens the queue, allowing enqueue operations to proceed. - /// - public void Open() + /// + /// Reopens the queue, allowing enqueue operations to proceed. + /// + public void Open() + { + lock (SyncRoot) { - lock (SyncRoot) - { - _isClosed = false; - } + _isClosed = false; } + } - /// - /// Blocks the current thread while the queue is full, unless it is closed. - /// - private void WaitWhileFull() + /// + /// Blocks the current thread while the queue is full, unless it is closed. + /// + private void WaitWhileFull() + { + while (InnerCollection.Count >= _maxSize && !_isClosed) { - while (InnerCollection.Count >= _maxSize && !_isClosed) - { - Monitor.Wait(SyncRoot); - } + Monitor.Wait(SyncRoot); } + } - /// - /// Blocks the current thread until the queue is empty or closed. - /// - public void WaitUntilEmpty() + /// + /// Blocks the current thread until the queue is empty or closed. + /// + public void WaitUntilEmpty() + { + lock (SyncRoot) { - lock (SyncRoot) - { - while (InnerCollection.Count > 0 && !_isClosed) - Monitor.Wait(SyncRoot); - } + while (InnerCollection.Count > 0 && !_isClosed) + Monitor.Wait(SyncRoot); } + } - /// - /// Adds an item to the queue, optionally forcing the enqueue even if the queue is full. - /// - /// The item to add to the queue. - /// If true, adds the item regardless of the maximum size; otherwise, waits if the queue is full. - public void Enqueue(T item, bool force = false) + /// + /// Adds an item to the queue, optionally forcing the enqueue even if the queue is full. + /// + /// The item to add to the queue. + /// If true, adds the item regardless of the maximum size; otherwise, waits if the queue is full. + public void Enqueue(T item, bool force = false) + { + lock (SyncRoot) { - lock (SyncRoot) + if (_isClosed) + return; + + if (!force && _maxSize != -1) { - if (_isClosed) - return; - - if (!force && _maxSize != -1) - { - if (InnerCollection.Count >= _maxSize) - WaitWhileFull(); - } - - OnEnqueue(item, force); - - if (InnerCollection.Count == 1) - { - // wake up any blocked dequeue - Monitor.PulseAll(SyncRoot); - } + if (InnerCollection.Count >= _maxSize) + WaitWhileFull(); } - } - /// - /// Performs the actual enqueue operation on the inner collection. - /// - /// The item to enqueue. - /// Indicates whether the enqueue is forced. - protected abstract void OnEnqueue(T item, bool force); - - /// - /// Performs the actual dequeue operation on the inner collection. - /// - /// The dequeued item. - protected abstract T OnDequeue(); - - /// - /// Retrieves, but does not remove, the head of the queue. - /// - /// The item at the head of the queue. - protected abstract T OnPeek(); - - /// - /// Removes and returns the item at the head of the queue. - /// - /// The dequeued item. - public T Dequeue() - { - TryDequeue(out T retVal, false); - return retVal; - } + OnEnqueue(item, force); - /// - /// Blocks the current thread while the queue is empty, based on the specified conditions. - /// - /// If true, exits if the queue is closed. - /// If true, blocks until an item is available; otherwise, returns immediately. - /// True if an item is available; otherwise, false. - private bool WaitWhileEmpty(bool exitOnClose, bool block) - { - while (InnerCollection.Count == 0) + if (InnerCollection.Count == 1) { - if (exitOnClose && _isClosed) - return false; - - if (!block) - return false; - - Monitor.Wait(SyncRoot); + // wake up any blocked dequeue + Monitor.PulseAll(SyncRoot); } - - return true; } + } - /// - /// Attempts to remove and return the item at the head of the queue. - /// - /// When this method returns, contains the dequeued item if successful; otherwise, the default value. - /// If true, exits if the queue is closed. - /// If true, blocks until an item is available; otherwise, returns immediately. - /// True if an item was dequeued; otherwise, false. - public bool TryDequeue(out T value, bool exitOnClose = true, bool block = true) - { - lock (SyncRoot) - { - if (!WaitWhileEmpty(exitOnClose, block)) - { - value = default; - return false; - } + /// + /// Performs the actual enqueue operation on the inner collection. + /// + /// The item to enqueue. + /// Indicates whether the enqueue is forced. + protected abstract void OnEnqueue(T item, bool force); - value = OnDequeue(); + /// + /// Performs the actual dequeue operation on the inner collection. + /// + /// The dequeued item. + protected abstract T OnDequeue(); - if (InnerCollection.Count == (_maxSize - 1) || InnerCollection.Count == 0) - { - // wake up any blocked enqueue - Monitor.PulseAll(SyncRoot); - } + /// + /// Retrieves, but does not remove, the head of the queue. + /// + /// The item at the head of the queue. + protected abstract T OnPeek(); - return true; - } - } + /// + /// Removes and returns the item at the head of the queue. + /// + /// The dequeued item. + public T Dequeue() + { + TryDequeue(out T retVal, false); + return retVal; + } - /// - /// Retrieves, but does not remove, the item at the head of the queue. - /// - /// The item at the head of the queue. - public T Peek() + /// + /// Blocks the current thread while the queue is empty, based on the specified conditions. + /// + /// If true, exits if the queue is closed. + /// If true, blocks until an item is available; otherwise, returns immediately. + /// True if an item is available; otherwise, false. + private bool WaitWhileEmpty(bool exitOnClose, bool block) + { + while (InnerCollection.Count == 0) { - TryPeek(out T retVal, false); - return retVal; + if (exitOnClose && _isClosed) + return false; + + if (!block) + return false; + + Monitor.Wait(SyncRoot); } - /// - /// Attempts to retrieve, but not remove, the item at the head of the queue. - /// - /// When this method returns, contains the peeked item if successful; otherwise, the default value. - /// If true, exits if the queue is closed. - /// If true, blocks until an item is available; otherwise, returns immediately. - /// True if an item was peeked; otherwise, false. - public bool TryPeek(out T value, bool exitOnClose = true, bool block = true) + return true; + } + + /// + /// Attempts to remove and return the item at the head of the queue. + /// + /// When this method returns, contains the dequeued item if successful; otherwise, the default value. + /// If true, exits if the queue is closed. + /// If true, blocks until an item is available; otherwise, returns immediately. + /// True if an item was dequeued; otherwise, false. + public bool TryDequeue(out T value, bool exitOnClose = true, bool block = true) + { + lock (SyncRoot) { - lock (SyncRoot) + if (!WaitWhileEmpty(exitOnClose, block)) { - if (!WaitWhileEmpty(exitOnClose, block)) - { - value = default; - return false; - } + value = default; + return false; + } - value = OnPeek(); + value = OnDequeue(); - return true; + if (InnerCollection.Count == (_maxSize - 1) || InnerCollection.Count == 0) + { + // wake up any blocked enqueue + Monitor.PulseAll(SyncRoot); } + + return true; } + } + + /// + /// Retrieves, but does not remove, the item at the head of the queue. + /// + /// The item at the head of the queue. + public T Peek() + { + TryPeek(out T retVal, false); + return retVal; + } - /// - /// Removes all items from the queue and notifies blocked threads. - /// - public void Clear() + /// + /// Attempts to retrieve, but not remove, the item at the head of the queue. + /// + /// When this method returns, contains the peeked item if successful; otherwise, the default value. + /// If true, exits if the queue is closed. + /// If true, blocks until an item is available; otherwise, returns immediately. + /// True if an item was peeked; otherwise, false. + public bool TryPeek(out T value, bool exitOnClose = true, bool block = true) + { + lock (SyncRoot) { - lock (SyncRoot) + if (!WaitWhileEmpty(exitOnClose, block)) { - InnerCollection.Clear(); - Monitor.PulseAll(SyncRoot); + value = default; + return false; } + + value = OnPeek(); + + return true; } + } - /// - /// Removes the specified item from the queue. This operation is not supported. - /// - /// The item to remove. - /// Always throws . - /// Thrown because removal of specific items is not supported. - bool ICollection.Remove(T item) + /// + /// Removes all items from the queue and notifies blocked threads. + /// + public void Clear() + { + lock (SyncRoot) { - throw new NotSupportedException(); + InnerCollection.Clear(); + Monitor.PulseAll(SyncRoot); } + } + + /// + /// Removes the specified item from the queue. This operation is not supported. + /// + /// The item to remove. + /// Always throws . + /// Thrown because removal of specific items is not supported. + bool ICollection.Remove(T item) + { + throw new NotSupportedException(); + } - /// - /// Gets a value indicating whether the collection is read-only. Always returns false. - /// - bool ICollection.IsReadOnly => false; + /// + /// Gets a value indicating whether the collection is read-only. Always returns false. + /// + bool ICollection.IsReadOnly => false; - /// - /// Adds an item to the queue using the method. - /// - /// The item to add. - void ICollection.Add(T item) - { - Enqueue(item); - } + /// + /// Adds an item to the queue using the method. + /// + /// The item to add. + void ICollection.Add(T item) + { + Enqueue(item); + } - /// - /// Determines whether the queue contains a specific item. - /// - /// The item to locate. - /// True if the item is found; otherwise, false. - bool ICollection.Contains(T item) - { - lock (SyncRoot) - return InnerCollection.Contains(item); - } + /// + /// Determines whether the queue contains a specific item. + /// + /// The item to locate. + /// True if the item is found; otherwise, false. + bool ICollection.Contains(T item) + { + lock (SyncRoot) + return InnerCollection.Contains(item); + } - /// - /// Copies the elements of the queue to an array, starting at the specified index. - /// - /// The destination array. - /// The zero-based index in the array at which copying begins. - void ICollection.CopyTo(T[] array, int arrayIndex) - { - lock (SyncRoot) - InnerCollection.CopyTo(array, arrayIndex); - } + /// + /// Copies the elements of the queue to an array, starting at the specified index. + /// + /// The destination array. + /// The zero-based index in the array at which copying begins. + void ICollection.CopyTo(T[] array, int arrayIndex) + { + lock (SyncRoot) + InnerCollection.CopyTo(array, arrayIndex); + } - /// - /// Returns an enumerator that iterates through the queue. - /// - /// An enumerator for the queue. - public IEnumerator GetEnumerator() - { - return InnerCollection.GetEnumerator(); - } + /// + /// Returns an enumerator that iterates through the queue. + /// + /// An enumerator for the queue. + public IEnumerator GetEnumerator() + { + return InnerCollection.GetEnumerator(); + } - /// - /// Returns an enumerator that iterates through the queue (non-generic version). - /// - /// An enumerator for the queue. - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + /// + /// Returns an enumerator that iterates through the queue (non-generic version). + /// + /// An enumerator for the queue. + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); } } \ No newline at end of file diff --git a/Collections/BaseCollection.cs b/Collections/BaseCollection.cs index c0f8afbe..63d6f171 100644 --- a/Collections/BaseCollection.cs +++ b/Collections/BaseCollection.cs @@ -1,576 +1,575 @@ -namespace Ecng.Collections +namespace Ecng.Collections; + +using System; +using System.Collections; +using System.Collections.Generic; + +using Ecng.Common; + +/// +/// Represents a base class for a generic collection with event notifications and inner collection management. +/// +/// The type of elements in the collection. +/// The type of the inner collection, which must implement . +[Serializable] +public abstract class BaseCollection : ICollection, ICollection, INotifyList, IList + where TCollection : ICollection { - using System; - using System.Collections; - using System.Collections.Generic; + /// + /// Initializes a new instance of the class with the specified inner collection. + /// + /// The inner collection to manage. + /// Thrown when is null. + protected BaseCollection(TCollection innerCollection) + { + if (innerCollection.IsNull()) + throw new ArgumentNullException(nameof(innerCollection)); + + InnerCollection = innerCollection; + } + + /// + /// Gets or sets a value indicating whether to check for null items in the collection. + /// + public bool CheckNullableItems { get; set; } + + /// + /// Gets the inner collection that stores the items. + /// + protected TCollection InnerCollection { get; } - using Ecng.Common; + /// + /// Retrieves an item at the specified index from the inner collection. + /// + /// The zero-based index of the item to retrieve. + /// The item at the specified index. + protected abstract TItem OnGetItem(int index); /// - /// Represents a base class for a generic collection with event notifications and inner collection management. + /// Inserts an item into the inner collection at the specified index. /// - /// The type of elements in the collection. - /// The type of the inner collection, which must implement . - [Serializable] - public abstract class BaseCollection : ICollection, ICollection, INotifyList, IList - where TCollection : ICollection + /// The zero-based index at which to insert the item. + /// The item to insert. + protected abstract void OnInsert(int index, TItem item); + + /// + /// Removes an item from the inner collection at the specified index. + /// + /// The zero-based index of the item to remove. + protected abstract void OnRemoveAt(int index); + + /// + /// Adds an item to the inner collection. + /// + /// The item to add. + protected virtual void OnAdd(TItem item) { - /// - /// Initializes a new instance of the class with the specified inner collection. - /// - /// The inner collection to manage. - /// Thrown when is null. - protected BaseCollection(TCollection innerCollection) - { - if (innerCollection.IsNull()) - throw new ArgumentNullException(nameof(innerCollection)); + InnerCollection.Add(item); + } - InnerCollection = innerCollection; - } + /// + /// Removes an item from the inner collection. + /// + /// The item to remove. + /// True if the item was removed; otherwise, false. + protected virtual bool OnRemove(TItem item) + { + return InnerCollection.Remove(item); + } + + /// + /// Removes all items from the inner collection. + /// + protected virtual void OnClear() + { + InnerCollection.Clear(); + } + + /// + /// Returns an enumerator that iterates through the collection (non-generic version). + /// + /// An enumerator for the collection. + IEnumerator IEnumerable.GetEnumerator() + => ((IEnumerable)this).GetEnumerator(); + + /// + /// Returns an enumerator that iterates through the collection. + /// + /// An enumerator for the collection. + public virtual IEnumerator GetEnumerator() + => InnerCollection.GetEnumerator(); + + /// + /// Gets the number of items in the collection. + /// + public virtual int Count => InnerCollection.Count; + + /// + /// Determines whether the collection contains a specific item. + /// + /// The item to locate. + /// True if the item is found; otherwise, false. + public virtual bool Contains(TItem item) => InnerCollection.Contains(item); + + /// + /// Validates the specified index to ensure it is within the valid range. + /// + /// The index to check. + /// Thrown when the index is less than 0 or greater than . + private void CheckIndex(int index) + { + if (index < 0 || index > Count) + throw new ArgumentOutOfRangeException(nameof(index), index, "Index has incorrect value."); + } - /// - /// Gets or sets a value indicating whether to check for null items in the collection. - /// - public bool CheckNullableItems { get; set; } - - /// - /// Gets the inner collection that stores the items. - /// - protected TCollection InnerCollection { get; } - - /// - /// Retrieves an item at the specified index from the inner collection. - /// - /// The zero-based index of the item to retrieve. - /// The item at the specified index. - protected abstract TItem OnGetItem(int index); - - /// - /// Inserts an item into the inner collection at the specified index. - /// - /// The zero-based index at which to insert the item. - /// The item to insert. - protected abstract void OnInsert(int index, TItem item); - - /// - /// Removes an item from the inner collection at the specified index. - /// - /// The zero-based index of the item to remove. - protected abstract void OnRemoveAt(int index); - - /// - /// Adds an item to the inner collection. - /// - /// The item to add. - protected virtual void OnAdd(TItem item) + /// + /// Gets or sets the item at the specified index. + /// + /// The zero-based index of the item. + /// The item at the specified index. + /// Thrown when the index is invalid. + public virtual TItem this[int index] + { + get => OnGetItem(index); + set { - InnerCollection.Add(item); + CheckIndex(index); + + if (index < Count) + { + RemoveAt(index); + Insert(index, value); + } + else + Add(value); } + } - /// - /// Removes an item from the inner collection. - /// - /// The item to remove. - /// True if the item was removed; otherwise, false. - protected virtual bool OnRemove(TItem item) + /// + /// Returns the index of the specified item in the collection. + /// + /// The item to locate. + /// The zero-based index of the item, or -1 if not found. + public abstract int IndexOf(TItem item); + + /// + /// Removes the item at the specified index. + /// + /// The zero-based index of the item to remove. + public virtual void RemoveAt(int index) + { + if (OnRemovingAt(index)) { - return InnerCollection.Remove(item); + OnRemoveAt(index); + OnRemovedAt(index); } + } + + /// + /// Inserts an item into the collection at the specified index. + /// + /// The zero-based index at which to insert the item. + /// The item to insert. + /// Thrown when the index is invalid. + public virtual void Insert(int index, TItem item) + { + CheckIndex(index); - /// - /// Removes all items from the inner collection. - /// - protected virtual void OnClear() + if (index == Count) + Add(item); + else { - InnerCollection.Clear(); + OnInserting(index, item); + OnInsert(index, item); + OnInserted(index, item); } + } + + /// + /// Gets a value indicating whether the collection is read-only. + /// + public virtual bool IsReadOnly => false; - /// - /// Returns an enumerator that iterates through the collection (non-generic version). - /// - /// An enumerator for the collection. - IEnumerator IEnumerable.GetEnumerator() - => ((IEnumerable)this).GetEnumerator(); - - /// - /// Returns an enumerator that iterates through the collection. - /// - /// An enumerator for the collection. - public virtual IEnumerator GetEnumerator() - => InnerCollection.GetEnumerator(); - - /// - /// Gets the number of items in the collection. - /// - public virtual int Count => InnerCollection.Count; - - /// - /// Determines whether the collection contains a specific item. - /// - /// The item to locate. - /// True if the item is found; otherwise, false. - public virtual bool Contains(TItem item) => InnerCollection.Contains(item); - - /// - /// Validates the specified index to ensure it is within the valid range. - /// - /// The index to check. - /// Thrown when the index is less than 0 or greater than . - private void CheckIndex(int index) + /// + /// Adds an item to the collection. + /// + /// The item to add. + /// Thrown when is null and is true. + public virtual void Add(TItem item) + { + if (CheckNullableItems && item.IsNull()) + throw new ArgumentNullException(nameof(item)); + + if (OnAdding(item)) { - if (index < 0 || index > Count) - throw new ArgumentOutOfRangeException(nameof(index), index, "Index has incorrect value."); + OnAdd(item); + OnAdded(item); } + } - /// - /// Gets or sets the item at the specified index. - /// - /// The zero-based index of the item. - /// The item at the specified index. - /// Thrown when the index is invalid. - public virtual TItem this[int index] + /// + /// Removes all items from the collection. + /// + public virtual void Clear() + { + if (OnClearing()) { - get => OnGetItem(index); - set - { - CheckIndex(index); - - if (index < Count) - { - RemoveAt(index); - Insert(index, value); - } - else - Add(value); - } + OnClear(); + OnCleared(); } + } - /// - /// Returns the index of the specified item in the collection. - /// - /// The item to locate. - /// The zero-based index of the item, or -1 if not found. - public abstract int IndexOf(TItem item); - - /// - /// Removes the item at the specified index. - /// - /// The zero-based index of the item to remove. - public virtual void RemoveAt(int index) + /// + /// Removes the specified item from the collection. + /// + /// The item to remove. + /// True if the item was removed; otherwise, false. + /// Thrown when is null and is true. + public virtual bool Remove(TItem item) + { + if (CheckNullableItems && item.IsNull()) + throw new ArgumentNullException(nameof(item)); + + if (OnRemoving(item)) { - if (OnRemovingAt(index)) + if (OnRemove(item)) { - OnRemoveAt(index); - OnRemovedAt(index); + OnRemoved(item); + return true; } } - /// - /// Inserts an item into the collection at the specified index. - /// - /// The zero-based index at which to insert the item. - /// The item to insert. - /// Thrown when the index is invalid. - public virtual void Insert(int index, TItem item) - { - CheckIndex(index); + return false; + } - if (index == Count) - Add(item); - else - { - OnInserting(index, item); - OnInsert(index, item); - OnInserted(index, item); - } - } + /// + /// Occurs before an item is added to the collection. + /// + public event Func Adding; - /// - /// Gets a value indicating whether the collection is read-only. - /// - public virtual bool IsReadOnly => false; - - /// - /// Adds an item to the collection. - /// - /// The item to add. - /// Thrown when is null and is true. - public virtual void Add(TItem item) - { - if (CheckNullableItems && item.IsNull()) - throw new ArgumentNullException(nameof(item)); + /// + /// Occurs after an item has been added to the collection. + /// + public event Action Added; - if (OnAdding(item)) - { - OnAdd(item); - OnAdded(item); - } - } + /// + /// Occurs before an item is removed from the collection. + /// + public event Func Removing; - /// - /// Removes all items from the collection. - /// - public virtual void Clear() - { - if (OnClearing()) - { - OnClear(); - OnCleared(); - } - } + /// + /// Occurs after an item has been removed from the collection. + /// + public event Action Removed; - /// - /// Removes the specified item from the collection. - /// - /// The item to remove. - /// True if the item was removed; otherwise, false. - /// Thrown when is null and is true. - public virtual bool Remove(TItem item) - { - if (CheckNullableItems && item.IsNull()) - throw new ArgumentNullException(nameof(item)); + /// + /// Occurs before an item is removed at the specified index. + /// + public event Func RemovingAt; - if (OnRemoving(item)) - { - if (OnRemove(item)) - { - OnRemoved(item); - return true; - } - } + /// + /// Occurs after an item has been removed at the specified index. + /// + public event Action RemovedAt; - return false; - } + /// + /// Occurs before the collection is cleared. + /// + public event Func Clearing; - /// - /// Occurs before an item is added to the collection. - /// - public event Func Adding; - - /// - /// Occurs after an item has been added to the collection. - /// - public event Action Added; - - /// - /// Occurs before an item is removed from the collection. - /// - public event Func Removing; - - /// - /// Occurs after an item has been removed from the collection. - /// - public event Action Removed; - - /// - /// Occurs before an item is removed at the specified index. - /// - public event Func RemovingAt; - - /// - /// Occurs after an item has been removed at the specified index. - /// - public event Action RemovedAt; - - /// - /// Occurs before the collection is cleared. - /// - public event Func Clearing; - - /// - /// Occurs after the collection has been cleared. - /// - public event Action Cleared; - - /// - /// Occurs before an item is inserted at the specified index. - /// - public event Func Inserting; - - /// - /// Occurs after an item has been inserted at the specified index. - /// - public event Action Inserted; - - /// - /// Occurs when the collection has been modified. - /// - public event Action Changed; - - /// - /// Invokes the event before inserting an item. - /// - /// The zero-based index at which to insert the item. - /// The item to insert. - /// True if the insertion should proceed; otherwise, false. - protected virtual bool OnInserting(int index, TItem item) - { - return Inserting?.Invoke(index, item) ?? true; - } + /// + /// Occurs after the collection has been cleared. + /// + public event Action Cleared; - /// - /// Invokes the event and notifies of a change after inserting an item. - /// - /// The zero-based index at which the item was inserted. - /// The inserted item. - protected virtual void OnInserted(int index, TItem item) - { - Inserted?.Invoke(index, item); - OnChanged(); - } + /// + /// Occurs before an item is inserted at the specified index. + /// + public event Func Inserting; - /// - /// Invokes the event before adding an item. - /// - /// The item to add. - /// True if the addition should proceed; otherwise, false. - protected virtual bool OnAdding(TItem item) - { - return Adding?.Invoke(item) ?? true; - } + /// + /// Occurs after an item has been inserted at the specified index. + /// + public event Action Inserted; - /// - /// Invokes the event and notifies of a change after adding an item. - /// - /// The added item. - protected virtual void OnAdded(TItem item) - { - Added?.Invoke(item); - OnChanged(); - } + /// + /// Occurs when the collection has been modified. + /// + public event Action Changed; - /// - /// Invokes the event before clearing the collection. - /// - /// True if the clearing should proceed; otherwise, false. - protected virtual bool OnClearing() - { - return Clearing?.Invoke() ?? true; - } + /// + /// Invokes the event before inserting an item. + /// + /// The zero-based index at which to insert the item. + /// The item to insert. + /// True if the insertion should proceed; otherwise, false. + protected virtual bool OnInserting(int index, TItem item) + { + return Inserting?.Invoke(index, item) ?? true; + } - /// - /// Invokes the event and notifies of a change after clearing the collection. - /// - protected virtual void OnCleared() - { - Cleared?.Invoke(); - OnChanged(); - } + /// + /// Invokes the event and notifies of a change after inserting an item. + /// + /// The zero-based index at which the item was inserted. + /// The inserted item. + protected virtual void OnInserted(int index, TItem item) + { + Inserted?.Invoke(index, item); + OnChanged(); + } - /// - /// Invokes the event before removing an item. - /// - /// The item to remove. - /// True if the removal should proceed; otherwise, false. - protected virtual bool OnRemoving(TItem item) - { - return Removing?.Invoke(item) ?? true; - } + /// + /// Invokes the event before adding an item. + /// + /// The item to add. + /// True if the addition should proceed; otherwise, false. + protected virtual bool OnAdding(TItem item) + { + return Adding?.Invoke(item) ?? true; + } - /// - /// Invokes the event and notifies of a change after removing an item. - /// - /// The removed item. - protected virtual void OnRemoved(TItem item) - { - Removed?.Invoke(item); - OnChanged(); - } + /// + /// Invokes the event and notifies of a change after adding an item. + /// + /// The added item. + protected virtual void OnAdded(TItem item) + { + Added?.Invoke(item); + OnChanged(); + } - /// - /// Invokes the event before removing an item at the specified index. - /// - /// The zero-based index of the item to remove. - /// True if the removal should proceed; otherwise, false. - protected virtual bool OnRemovingAt(int index) - { - return RemovingAt?.Invoke(index) ?? true; - } + /// + /// Invokes the event before clearing the collection. + /// + /// True if the clearing should proceed; otherwise, false. + protected virtual bool OnClearing() + { + return Clearing?.Invoke() ?? true; + } - /// - /// Invokes the event and notifies of a change after removing an item at the specified index. - /// - /// The zero-based index of the removed item. - protected virtual void OnRemovedAt(int index) - { - RemovedAt?.Invoke(index); - OnChanged(); - } + /// + /// Invokes the event and notifies of a change after clearing the collection. + /// + protected virtual void OnCleared() + { + Cleared?.Invoke(); + OnChanged(); + } - /// - /// Invokes the event to notify of a modification in the collection. - /// - protected virtual void OnChanged() - { - Changed?.Invoke(); - } + /// + /// Invokes the event before removing an item. + /// + /// The item to remove. + /// True if the removal should proceed; otherwise, false. + protected virtual bool OnRemoving(TItem item) + { + return Removing?.Invoke(item) ?? true; + } - #region IList Members + /// + /// Invokes the event and notifies of a change after removing an item. + /// + /// The removed item. + protected virtual void OnRemoved(TItem item) + { + Removed?.Invoke(item); + OnChanged(); + } - /// - /// Determines whether the collection contains a specific value (non-generic). - /// - /// The value to locate. - /// True if the value is found and compatible; otherwise, false. - bool IList.Contains(object value) - { - if (!IsCompatible(value)) - return false; + /// + /// Invokes the event before removing an item at the specified index. + /// + /// The zero-based index of the item to remove. + /// True if the removal should proceed; otherwise, false. + protected virtual bool OnRemovingAt(int index) + { + return RemovingAt?.Invoke(index) ?? true; + } - return Contains((TItem)value); - } + /// + /// Invokes the event and notifies of a change after removing an item at the specified index. + /// + /// The zero-based index of the removed item. + protected virtual void OnRemovedAt(int index) + { + RemovedAt?.Invoke(index); + OnChanged(); + } - /// - /// Adds a value to the collection (non-generic). - /// - /// The value to add. - /// The new count of items in the collection. - /// Thrown when is null and incompatible with . - int IList.Add(object value) - { - if (!IsCompatible(value)) - throw new ArgumentNullException(nameof(value)); + /// + /// Invokes the event to notify of a modification in the collection. + /// + protected virtual void OnChanged() + { + Changed?.Invoke(); + } - Add((TItem)value); - return Count; - } + #region IList Members - /// - /// Gets a value indicating whether the collection is read-only (non-generic). - /// - bool IList.IsReadOnly => false; - - /// - /// Gets or sets the item at the specified index (non-generic). - /// - /// The zero-based index of the item. - /// The item at the specified index. - /// Thrown when setting a value that is null and incompatible with . - object IList.this[int index] - { - get => this[index]; - set - { - if (!IsCompatible(value)) - throw new ArgumentNullException(nameof(value)); + /// + /// Determines whether the collection contains a specific value (non-generic). + /// + /// The value to locate. + /// True if the value is found and compatible; otherwise, false. + bool IList.Contains(object value) + { + if (!IsCompatible(value)) + return false; - this[index] = (TItem)value; - } - } + return Contains((TItem)value); + } - /// - /// Removes all items from the collection (non-generic). - /// - void IList.Clear() - { - ((ICollection)this).Clear(); - } + /// + /// Adds a value to the collection (non-generic). + /// + /// The value to add. + /// The new count of items in the collection. + /// Thrown when is null and incompatible with . + int IList.Add(object value) + { + if (!IsCompatible(value)) + throw new ArgumentNullException(nameof(value)); - /// - /// Returns the index of a specific value in the collection (non-generic). - /// - /// The value to locate. - /// The zero-based index of the value, or -1 if not found or incompatible. - int IList.IndexOf(object value) - { - if (!IsCompatible(value)) - return -1; + Add((TItem)value); + return Count; + } - return IndexOf((TItem)value); - } + /// + /// Gets a value indicating whether the collection is read-only (non-generic). + /// + bool IList.IsReadOnly => false; - /// - /// Inserts a value into the collection at the specified index (non-generic). - /// - /// The zero-based index at which to insert the value. - /// The value to insert. - /// Thrown when is null and incompatible with . - void IList.Insert(int index, object value) + /// + /// Gets or sets the item at the specified index (non-generic). + /// + /// The zero-based index of the item. + /// The item at the specified index. + /// Thrown when setting a value that is null and incompatible with . + object IList.this[int index] + { + get => this[index]; + set { if (!IsCompatible(value)) throw new ArgumentNullException(nameof(value)); - Insert(index, (TItem)value); + this[index] = (TItem)value; } + } - /// - /// Removes a specific value from the collection (non-generic). - /// - /// The value to remove. - void IList.Remove(object value) - { - if (!IsCompatible(value)) - return; + /// + /// Removes all items from the collection (non-generic). + /// + void IList.Clear() + { + ((ICollection)this).Clear(); + } - Remove((TItem)value); - } + /// + /// Returns the index of a specific value in the collection (non-generic). + /// + /// The value to locate. + /// The zero-based index of the value, or -1 if not found or incompatible. + int IList.IndexOf(object value) + { + if (!IsCompatible(value)) + return -1; - /// - /// Removes the item at the specified index (non-generic). - /// - /// The zero-based index of the item to remove. - void IList.RemoveAt(int index) - { - RemoveAt(index); - } + return IndexOf((TItem)value); + } - /// - /// Gets a value indicating whether the collection has a fixed size (non-generic). Always returns false. - /// - bool IList.IsFixedSize => false; - - #endregion - - private static readonly bool _isValueType = typeof(TItem).IsValueType; - - /// - /// Determines whether a value is compatible with the collection's item type. - /// - /// The value to check. - /// True if the value is compatible; otherwise, false. - private static bool IsCompatible(object value) => !_isValueType || value != null; - - /// - /// Gets a value indicating whether the collection is synchronized (non-generic). Always returns false. - /// - bool ICollection.IsSynchronized => false; - - /// - /// Gets the synchronization root object (non-generic). Returns the current instance. - /// - object ICollection.SyncRoot => this; - - /// - /// Copies the elements of the collection to an array (non-generic). - /// - /// The destination array. - /// The zero-based index in the array at which copying begins. - void ICollection.CopyTo(Array array, int index) - => CopyTo((TItem[])array, index); - - /// - /// Copies the elements of the collection to an array. - /// - /// The destination array. - /// The zero-based index in the array at which copying begins. - public void CopyTo(TItem[] array, int index) - { - int count = Count; + /// + /// Inserts a value into the collection at the specified index (non-generic). + /// + /// The zero-based index at which to insert the value. + /// The value to insert. + /// Thrown when is null and incompatible with . + void IList.Insert(int index, object value) + { + if (!IsCompatible(value)) + throw new ArgumentNullException(nameof(value)); - if (count == 0) - return; + Insert(index, (TItem)value); + } - var i = 0; - foreach (var item in (ICollection)this) - { - if (i >= count) - break; + /// + /// Removes a specific value from the collection (non-generic). + /// + /// The value to remove. + void IList.Remove(object value) + { + if (!IsCompatible(value)) + return; - array[index] = item; + Remove((TItem)value); + } - index++; - i++; - } + /// + /// Removes the item at the specified index (non-generic). + /// + /// The zero-based index of the item to remove. + void IList.RemoveAt(int index) + { + RemoveAt(index); + } + + /// + /// Gets a value indicating whether the collection has a fixed size (non-generic). Always returns false. + /// + bool IList.IsFixedSize => false; + + #endregion + + private static readonly bool _isValueType = typeof(TItem).IsValueType; + + /// + /// Determines whether a value is compatible with the collection's item type. + /// + /// The value to check. + /// True if the value is compatible; otherwise, false. + private static bool IsCompatible(object value) => !_isValueType || value != null; + + /// + /// Gets a value indicating whether the collection is synchronized (non-generic). Always returns false. + /// + bool ICollection.IsSynchronized => false; + + /// + /// Gets the synchronization root object (non-generic). Returns the current instance. + /// + object ICollection.SyncRoot => this; + + /// + /// Copies the elements of the collection to an array (non-generic). + /// + /// The destination array. + /// The zero-based index in the array at which copying begins. + void ICollection.CopyTo(Array array, int index) + => CopyTo((TItem[])array, index); + + /// + /// Copies the elements of the collection to an array. + /// + /// The destination array. + /// The zero-based index in the array at which copying begins. + public void CopyTo(TItem[] array, int index) + { + int count = Count; + + if (count == 0) + return; + + var i = 0; + foreach (var item in (ICollection)this) + { + if (i >= count) + break; + + array[index] = item; + + index++; + i++; } } } \ No newline at end of file diff --git a/Collections/BaseEnumerator.cs b/Collections/BaseEnumerator.cs index cdefdc59..8f6d6626 100644 --- a/Collections/BaseEnumerator.cs +++ b/Collections/BaseEnumerator.cs @@ -1,73 +1,72 @@ -namespace Ecng.Collections -{ - using System; - using System.Collections.Generic; +namespace Ecng.Collections; + +using System; +using System.Collections.Generic; - using Ecng.Common; +using Ecng.Common; +/// +/// Represents an abstract base class for an enumerator over a generic enumerable source. +/// +/// The type of the enumerable source, which must implement . +/// The type of elements in the enumerable source. +public abstract class BaseEnumerator : SimpleEnumerator + where TEnumerable : IEnumerable +{ /// - /// Represents an abstract base class for an enumerator over a generic enumerable source. + /// Initializes a new instance of the class with the specified source. /// - /// The type of the enumerable source, which must implement . - /// The type of elements in the enumerable source. - public abstract class BaseEnumerator : SimpleEnumerator - where TEnumerable : IEnumerable + /// The enumerable source to iterate over. + /// Thrown when is null. + protected BaseEnumerator(TEnumerable source) { - /// - /// Initializes a new instance of the class with the specified source. - /// - /// The enumerable source to iterate over. - /// Thrown when is null. - protected BaseEnumerator(TEnumerable source) - { - if (source.IsNull()) - throw new ArgumentException(nameof(source)); + if (source.IsNull()) + throw new ArgumentException(nameof(source)); - Source = source; - Reset(); - } - - /// - /// Gets or sets the enumerable source being iterated over. - /// - public TEnumerable Source { get; private set; } + Source = source; + Reset(); + } - /// - /// Disposes of managed resources by resetting the enumerator and clearing the source. - /// - protected override void DisposeManaged() - { - Reset(); - Source = default; - } + /// + /// Gets or sets the enumerable source being iterated over. + /// + public TEnumerable Source { get; private set; } - /// - /// Advances the enumerator to the next element of the collection. - /// - /// True if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection. - /// Thrown if the enumerator has been disposed. - public override bool MoveNext() - { - ThrowIfDisposed(); + /// + /// Disposes of managed resources by resetting the enumerator and clearing the source. + /// + protected override void DisposeManaged() + { + Reset(); + Source = default; + } - var canProcess = true; - Current = ProcessMove(ref canProcess); - return canProcess; - } + /// + /// Advances the enumerator to the next element of the collection. + /// + /// True if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection. + /// Thrown if the enumerator has been disposed. + public override bool MoveNext() + { + ThrowIfDisposed(); - /// - /// Sets the enumerator to its initial position, which is before the first element in the collection. - /// - public override void Reset() - { - Current = default; - } + var canProcess = true; + Current = ProcessMove(ref canProcess); + return canProcess; + } - /// - /// Processes the movement to the next element and determines whether iteration can continue. - /// - /// A reference parameter indicating whether the enumerator can proceed; set to false if the end is reached. - /// The next item in the collection, or the default value if no further items are available. - protected abstract TItem ProcessMove(ref bool canProcess); + /// + /// Sets the enumerator to its initial position, which is before the first element in the collection. + /// + public override void Reset() + { + Current = default; } + + /// + /// Processes the movement to the next element and determines whether iteration can continue. + /// + /// A reference parameter indicating whether the enumerator can proceed; set to false if the end is reached. + /// The next item in the collection, or the default value if no further items are available. + protected abstract TItem ProcessMove(ref bool canProcess); } \ No newline at end of file diff --git a/Collections/BaseList.cs b/Collections/BaseList.cs index 378c1980..2d67faf4 100644 --- a/Collections/BaseList.cs +++ b/Collections/BaseList.cs @@ -1,60 +1,59 @@ -namespace Ecng.Collections -{ - using System; - using System.Collections.Generic; +namespace Ecng.Collections; + +using System; +using System.Collections.Generic; +/// +/// Represents a base class for a strongly-typed list of items. +/// +/// The type of elements in the list. +[Serializable] +public abstract class BaseList(IList innerList) : BaseCollection>(innerList) +{ /// - /// Represents a base class for a strongly-typed list of items. + /// Initializes a new instance of the class with an empty inner list. /// - /// The type of elements in the list. - [Serializable] - public abstract class BaseList(IList innerList) : BaseCollection>(innerList) + protected BaseList() + : this([]) { - /// - /// Initializes a new instance of the class with an empty inner list. - /// - protected BaseList() - : this([]) - { - } + } - /// - /// Retrieves the item at the specified index from the inner list. - /// - /// The zero-based index of the item to retrieve. - /// The item at the specified index. - protected override TItem OnGetItem(int index) - { - return InnerCollection[index]; - } + /// + /// Retrieves the item at the specified index from the inner list. + /// + /// The zero-based index of the item to retrieve. + /// The item at the specified index. + protected override TItem OnGetItem(int index) + { + return InnerCollection[index]; + } - /// - /// Inserts an item into the inner list at the specified index. - /// - /// The zero-based index at which the item should be inserted. - /// The item to insert into the list. - protected override void OnInsert(int index, TItem item) - { - InnerCollection.Insert(index, item); - } + /// + /// Inserts an item into the inner list at the specified index. + /// + /// The zero-based index at which the item should be inserted. + /// The item to insert into the list. + protected override void OnInsert(int index, TItem item) + { + InnerCollection.Insert(index, item); + } - /// - /// Removes the item at the specified index from the inner list. - /// - /// The zero-based index of the item to remove. - protected override void OnRemoveAt(int index) - { - InnerCollection.RemoveAt(index); - } + /// + /// Removes the item at the specified index from the inner list. + /// + /// The zero-based index of the item to remove. + protected override void OnRemoveAt(int index) + { + InnerCollection.RemoveAt(index); + } - /// - /// Determines the index of a specific item in the inner list. - /// - /// The item to locate in the list. - /// The index of the item if found in the list; otherwise, -1. - public override int IndexOf(TItem item) - { - return InnerCollection.IndexOf(item); - } + /// + /// Determines the index of a specific item in the inner list. + /// + /// The item to locate in the list. + /// The index of the item if found in the list; otherwise, -1. + public override int IndexOf(TItem item) + { + return InnerCollection.IndexOf(item); } } \ No newline at end of file diff --git a/Collections/BaseOrderedBlockingQueue.cs b/Collections/BaseOrderedBlockingQueue.cs index 2f6d25cd..4cf3f8ef 100644 --- a/Collections/BaseOrderedBlockingQueue.cs +++ b/Collections/BaseOrderedBlockingQueue.cs @@ -1,64 +1,63 @@ -namespace Ecng.Collections -{ - using System.Collections.Generic; +namespace Ecng.Collections; + +using System.Collections.Generic; +/// +/// Represents a base class for an ordered blocking queue that sorts elements based on a specified key. +/// +/// The type used to determine the sort order of elements. +/// The type of the values stored in the queue. +/// The type of the inner collection, which must implement and for tuples of and . +public abstract class BaseOrderedBlockingQueue(TCollection collection) : + BaseBlockingQueue<(TSort sort, TValue elem), TCollection>(collection) + where TCollection : ICollection<(TSort, TValue)>, IQueue<(TSort, TValue)> +{ /// - /// Represents a base class for an ordered blocking queue that sorts elements based on a specified key. + /// Attempts to remove and return the next value from the queue. /// - /// The type used to determine the sort order of elements. - /// The type of the values stored in the queue. - /// The type of the inner collection, which must implement and for tuples of and . - public abstract class BaseOrderedBlockingQueue(TCollection collection) : - BaseBlockingQueue<(TSort sort, TValue elem), TCollection>(collection) - where TCollection : ICollection<(TSort, TValue)>, IQueue<(TSort, TValue)> + /// When this method returns, contains the dequeued value if successful; otherwise, the default value of . + /// If true, exits immediately if the queue is closed; otherwise, waits for an item. + /// If true, blocks until an item is available; otherwise, returns immediately. + /// True if a value was successfully dequeued; otherwise, false. + public bool TryDequeue(out TValue value, bool exitOnClose = true, bool block = true) { - /// - /// Attempts to remove and return the next value from the queue. - /// - /// When this method returns, contains the dequeued value if successful; otherwise, the default value of . - /// If true, exits immediately if the queue is closed; otherwise, waits for an item. - /// If true, blocks until an item is available; otherwise, returns immediately. - /// True if a value was successfully dequeued; otherwise, false. - public bool TryDequeue(out TValue value, bool exitOnClose = true, bool block = true) + if (base.TryDequeue(out var pair, exitOnClose, block)) { - if (base.TryDequeue(out var pair, exitOnClose, block)) - { - value = pair.elem; - return true; - } - - value = default; - return false; + value = pair.elem; + return true; } - /// - /// Adds a new value to the queue with the specified sort order. - /// - /// The sort key determining the order of the value in the queue. - /// The value to add to the queue. - protected void Enqueue(TSort sort, TValue value) - => Enqueue(new(sort, value)); + value = default; + return false; + } - /// - /// Adds an item to the inner collection when enqueuing. - /// - /// The tuple containing the sort key and value to enqueue. - /// If true, forces the item to be enqueued even if the queue is full; otherwise, respects the maximum size limit. - protected override void OnEnqueue((TSort, TValue) item, bool force) - => InnerCollection.Enqueue(item); + /// + /// Adds a new value to the queue with the specified sort order. + /// + /// The sort key determining the order of the value in the queue. + /// The value to add to the queue. + protected void Enqueue(TSort sort, TValue value) + => Enqueue(new(sort, value)); - /// - /// Removes and returns the next item from the inner collection. - /// - /// The tuple containing the sort key and value of the dequeued item. - protected override (TSort, TValue) OnDequeue() - => InnerCollection.Dequeue(); + /// + /// Adds an item to the inner collection when enqueuing. + /// + /// The tuple containing the sort key and value to enqueue. + /// If true, forces the item to be enqueued even if the queue is full; otherwise, respects the maximum size limit. + protected override void OnEnqueue((TSort, TValue) item, bool force) + => InnerCollection.Enqueue(item); - /// - /// Retrieves, but does not remove, the next item from the inner collection. - /// - /// The tuple containing the sort key and value of the item at the head of the queue. - protected override (TSort, TValue) OnPeek() - => InnerCollection.Peek(); - } + /// + /// Removes and returns the next item from the inner collection. + /// + /// The tuple containing the sort key and value of the dequeued item. + protected override (TSort, TValue) OnDequeue() + => InnerCollection.Dequeue(); + + /// + /// Retrieves, but does not remove, the next item from the inner collection. + /// + /// The tuple containing the sort key and value of the item at the head of the queue. + protected override (TSort, TValue) OnPeek() + => InnerCollection.Peek(); } \ No newline at end of file diff --git a/Collections/BitArrayReader.cs b/Collections/BitArrayReader.cs index 1bef2adc..30a6f688 100644 --- a/Collections/BitArrayReader.cs +++ b/Collections/BitArrayReader.cs @@ -1,169 +1,256 @@ -namespace Ecng.Collections -{ - using System; - using System.IO; +namespace Ecng.Collections; + +using System; +using System.IO; - using Ecng.Common; +using Ecng.Common; + +/// +/// Provides a reader for bit-level data from a stream. +/// +public class BitArrayReader +{ + private int _bitOffset; + private long _dataOffset; + private readonly ulong[] _data; + private readonly Stream _underlyingStream; /// - /// Provides a reader for bit-level data from a stream. + /// Initializes a new instance of the class with the specified underlying stream. /// - public class BitArrayReader + /// The stream to read bit-level data from. + /// Thrown if is null. + public BitArrayReader(Stream underlyingStream) { - private int _bitOffset; - private long _dataOffset; - private readonly ulong[] _data; - private readonly Stream _underlyingStream; - - /// - /// Initializes a new instance of the class with the specified underlying stream. - /// - /// The stream to read bit-level data from. - /// Thrown if is null. - public BitArrayReader(Stream underlyingStream) - { - _underlyingStream = underlyingStream ?? throw new ArgumentNullException(nameof(underlyingStream)); + _underlyingStream = underlyingStream ?? throw new ArgumentNullException(nameof(underlyingStream)); - // TODO - var bytes = underlyingStream.To(); + // TODO + var bytes = underlyingStream.To(); - _data = new ulong[bytes.Length / 8 + 2]; - Buffer.BlockCopy(bytes, 0, _data, 0, bytes.Length); - } + _data = new ulong[bytes.Length / 8 + 2]; + Buffer.BlockCopy(bytes, 0, _data, 0, bytes.Length); + } - /// - /// Gets or sets the current bit offset within the stream. - /// - /// Thrown if the value is negative. - public long Offset - { + /// + /// Gets or sets the current bit offset within the stream. + /// + /// Thrown if the value is negative. + public long Offset + { #pragma warning disable CS0675 // Bitwise-or operator used on a sign-extended operand - get => (_dataOffset << 6) | _bitOffset; + get => (_dataOffset << 6) | _bitOffset; #pragma warning restore CS0675 // Bitwise-or operator used on a sign-extended operand - set - { - if (value < 0)// || value >= _bits.Length) - throw new ArgumentOutOfRangeException(nameof(value)); + set + { + if (value < 0)// || value >= _bits.Length) + throw new ArgumentOutOfRangeException(nameof(value)); - _dataOffset = value >> 6; - _bitOffset = (int)(value & 63); - } + _dataOffset = value >> 6; + _bitOffset = (int)(value & 63); } + } - /// - /// Retrieves the 64-bit value at the specified offset. - /// - /// The offset to read from. - /// The 64-bit value at the specified offset. - private ulong Get(long offset) + /// + /// Retrieves the 64-bit value at the specified offset. + /// + /// The offset to read from. + /// The 64-bit value at the specified offset. + private ulong Get(long offset) + { + return _data[offset]; + } + + /// + /// Reads a single bit from the stream. + /// + /// The value of the bit read. + public bool Read() + { + var b = Get(_dataOffset); + + var value = ((b >> _bitOffset) & 1) != 0; + + if (_bitOffset == 63) { - return _data[offset]; + _bitOffset = 0; + _dataOffset++; } + else + _bitOffset++; - /// - /// Reads a single bit from the stream. - /// - /// The value of the bit read. - public bool Read() - { - var b = Get(_dataOffset); + return value; + } - var value = ((b >> _bitOffset) & 1) != 0; + /// + /// Reads an array of bits from the stream. + /// + /// The number of bits to read. + /// An array of boolean values representing the bits read. + public bool[] ReadArray(int count) + { + var retVal = new bool[count]; - if (_bitOffset == 63) - { - _bitOffset = 0; - _dataOffset++; - } - else - _bitOffset++; + for (var i = 0; i < count; i++) + retVal[i] = Read(); - return value; - } + return retVal; + } - /// - /// Reads an array of bits from the stream. - /// - /// The number of bits to read. - /// An array of boolean values representing the bits read. - public bool[] ReadArray(int count) - { - var retVal = new bool[count]; + /// + /// Reads a specified number of bits from the stream as an integer. + /// + /// The number of bits to read. + /// The integer value represented by the bits read. + /// Thrown if is invalid. + public int Read(int count) + { + return (int)ReadLong(count); + } - for (var i = 0; i < count; i++) - retVal[i] = Read(); + /// + /// Moves the current bit offset by the specified number of bits. + /// + /// The number of bits to move the offset by. + public void Seek(int offset) + { + var newOffset = _bitOffset + offset; + _dataOffset += newOffset >> 6; + _bitOffset = newOffset & 63; + } - return retVal; - } + /// + /// Reads a specified number of bits from the stream as a long integer. + /// + /// The number of bits to read. + /// The long integer value represented by the bits read. + /// Thrown if is invalid. + public long ReadLong(int count) + { + if (count <= 0 || count > 64) + throw new ArgumentOutOfRangeException(nameof(count), count, "Invalid count value."); + + var bitOffset = _bitOffset; + + var value = Get(_dataOffset) >> bitOffset; + + var shift = 64 - bitOffset; + if (shift < count) + value |= Get(_dataOffset + 1) << shift; + + bitOffset += count; + _dataOffset += bitOffset >> 6; + _bitOffset = bitOffset & 63; + + value &= ulong.MaxValue >> (64 - count); + + return (long)value; + } + + /// + /// Reads an integer value from the stream using a variable-length encoding. + /// + /// The integer value read from the stream. + public int ReadInt() + { + var bits = Get(_dataOffset); + var bitOffset = _bitOffset; - /// - /// Reads a specified number of bits from the stream as an integer. - /// - /// The number of bits to read. - /// The integer value represented by the bits read. - /// Thrown if is invalid. - public int Read(int count) + bits >>= bitOffset; + + if (bitOffset > 0) + bits |= Get(_dataOffset + 1) << (64 - bitOffset); // честные 64 бита в битс + + var value = 0; + + int seek; + + if ((bits & 1) == 0) { - return (int)ReadLong(count); + seek = 1; } - - /// - /// Moves the current bit offset by the specified number of bits. - /// - /// The number of bits to move the offset by. - public void Seek(int offset) + else if ((bits & 4) == 0) { - var newOffset = _bitOffset + offset; - _dataOffset += newOffset >> 6; - _bitOffset = newOffset & 63; + seek = 3; + value = 1; } - - /// - /// Reads a specified number of bits from the stream as a long integer. - /// - /// The number of bits to read. - /// The long integer value represented by the bits read. - /// Thrown if is invalid. - public long ReadLong(int count) + else if ((bits & 8) == 0) { - if (count <= 0 || count > 64) - throw new ArgumentOutOfRangeException(nameof(count), count, "Invalid count value."); - - var bitOffset = _bitOffset; + seek = 4 + 4; + value = ((int)(uint.MaxValue >> (32 - 4))) & (int)(bits >> 4); + } + else if ((bits & 16) == 0) + { + seek = 5 + 8; + value = ((int)(uint.MaxValue >> (32 - 8))) & (int)(bits >> 5); + } + else if ((bits & 32) == 0) + { + seek = 6 + 16; + value = ((int)(uint.MaxValue >> (32 - 16))) & (int)(bits >> 6); + } + else if ((bits & 64) == 0) + { + seek = 7 + 24; + value = ((int)(uint.MaxValue >> (32 - 24))) & (int)(bits >> 7); + } + else + { + seek = 7 + 32; + value = (int)(bits >> 7); + } - var value = Get(_dataOffset) >> bitOffset; + value = (bits & 2) != 0 ? value : -value; - var shift = 64 - bitOffset; - if (shift < count) - value |= Get(_dataOffset + 1) << shift; + bitOffset += seek; + _dataOffset += (bitOffset >> 6); + _bitOffset = bitOffset & 63; - bitOffset += count; - _dataOffset += bitOffset >> 6; - _bitOffset = bitOffset & 63; + return value; + } - value &= ulong.MaxValue >> (64 - count); + /// + /// Reads a long integer value from the stream using a variable-length encoding. + /// + /// The long integer value read from the stream. + public long ReadLong() + { + var offset = _dataOffset; + var bitOffset = _bitOffset; + var bits = Get(offset) >> bitOffset; - return (long)value; - } + if (bitOffset > 0) + bits |= Get(offset + 1) << (64 - bitOffset); - /// - /// Reads an integer value from the stream using a variable-length encoding. - /// - /// The integer value read from the stream. - public int ReadInt() + long value; + if ((bits & 1) != 0) { - var bits = Get(_dataOffset); - var bitOffset = _bitOffset; + var isPositive = (bits & 2) != 0; + + bitOffset += 2; - bits >>= bitOffset; + offset += bitOffset >> 6; + bitOffset &= 63; + bits = Get(offset) >> bitOffset; if (bitOffset > 0) - bits |= Get(_dataOffset + 1) << (64 - bitOffset); // честные 64 бита в битс + bits |= Get(offset + 1) << (64 - bitOffset); + + bitOffset += 63; - var value = 0; + value = (long)(bits & (ulong.MaxValue >> (64 - 63))); + + if (!isPositive) + value = -value; + } + else + { + bitOffset += 1; + bits >>= 1; int seek; + value = 0; + if ((bits & 1) == 0) { seek = 1; @@ -200,122 +287,34 @@ public int ReadInt() } value = (bits & 2) != 0 ? value : -value; - bitOffset += seek; - _dataOffset += (bitOffset >> 6); - _bitOffset = bitOffset & 63; - - return value; } - /// - /// Reads a long integer value from the stream using a variable-length encoding. - /// - /// The long integer value read from the stream. - public long ReadLong() - { - var offset = _dataOffset; - var bitOffset = _bitOffset; - var bits = Get(offset) >> bitOffset; - - if (bitOffset > 0) - bits |= Get(offset + 1) << (64 - bitOffset); + offset += bitOffset >> 6; + bitOffset &= 63; + _dataOffset = offset; + _bitOffset = bitOffset; - long value; - if ((bits & 1) != 0) - { - var isPositive = (bits & 2) != 0; - - bitOffset += 2; - - offset += bitOffset >> 6; - bitOffset &= 63; - bits = Get(offset) >> bitOffset; - - if (bitOffset > 0) - bits |= Get(offset + 1) << (64 - bitOffset); - - bitOffset += 63; - - value = (long)(bits & (ulong.MaxValue >> (64 - 63))); - - if (!isPositive) - value = -value; - } - else - { - bitOffset += 1; - bits >>= 1; - - int seek; - - value = 0; - - if ((bits & 1) == 0) - { - seek = 1; - } - else if ((bits & 4) == 0) - { - seek = 3; - value = 1; - } - else if ((bits & 8) == 0) - { - seek = 4 + 4; - value = ((int)(uint.MaxValue >> (32 - 4))) & (int)(bits >> 4); - } - else if ((bits & 16) == 0) - { - seek = 5 + 8; - value = ((int)(uint.MaxValue >> (32 - 8))) & (int)(bits >> 5); - } - else if ((bits & 32) == 0) - { - seek = 6 + 16; - value = ((int)(uint.MaxValue >> (32 - 16))) & (int)(bits >> 6); - } - else if ((bits & 64) == 0) - { - seek = 7 + 24; - value = ((int)(uint.MaxValue >> (32 - 24))) & (int)(bits >> 7); - } - else - { - seek = 7 + 32; - value = (int)(bits >> 7); - } - - value = (bits & 2) != 0 ? value : -value; - bitOffset += seek; - } - - offset += bitOffset >> 6; - bitOffset &= 63; - _dataOffset = offset; - _bitOffset = bitOffset; - - return value; - } + return value; + } - /// - /// Reads a decimal value from the stream. - /// - /// The decimal value read from the stream. - public decimal ReadDecimal() - { - var isPos = Read(); + /// + /// Reads a decimal value from the stream. + /// + /// The decimal value read from the stream. + public decimal ReadDecimal() + { + var isPos = Read(); - var i1 = ReadInt(); - var i2 = ReadInt(); - var i3 = ReadInt(); - var i4 = ReadInt() << 16; - var dec = new[] { i1, i2, i3, i4 }.To(); + var i1 = ReadInt(); + var i2 = ReadInt(); + var i3 = ReadInt(); + var i4 = ReadInt() << 16; + var dec = new[] { i1, i2, i3, i4 }.To(); - if (!isPos) - dec = -dec; + if (!isPos) + dec = -dec; - return dec; - } + return dec; } } \ No newline at end of file diff --git a/Collections/BitArrayWriter.cs b/Collections/BitArrayWriter.cs index 7825d5f2..db4d25b8 100644 --- a/Collections/BitArrayWriter.cs +++ b/Collections/BitArrayWriter.cs @@ -1,187 +1,186 @@ -namespace Ecng.Collections -{ - using System; - using System.IO; +namespace Ecng.Collections; + +using System; +using System.IO; - using Ecng.Common; +using Ecng.Common; + +/// +/// Provides a writer for bit-level data to a stream. +/// +public class BitArrayWriter(Stream underlyingStream) : Disposable +{ + private readonly Stream _underlyingStream = underlyingStream ?? throw new ArgumentNullException(nameof(underlyingStream)); + private int _temp; + private int _bitOffset; /// - /// Provides a writer for bit-level data to a stream. + /// Flushes the current byte to the underlying stream and resets the buffer. /// - public class BitArrayWriter(Stream underlyingStream) : Disposable + private void Flush() { - private readonly Stream _underlyingStream = underlyingStream ?? throw new ArgumentNullException(nameof(underlyingStream)); - private int _temp; - private int _bitOffset; - - /// - /// Flushes the current byte to the underlying stream and resets the buffer. - /// - private void Flush() - { - _underlyingStream.WriteByte((byte)_temp); - _temp = 0; - _bitOffset = 0; - } + _underlyingStream.WriteByte((byte)_temp); + _temp = 0; + _bitOffset = 0; + } - /// - /// Disposes the writer, ensuring any remaining bits are flushed to the stream. - /// - protected override void DisposeManaged() - { - if (_bitOffset > 0) - Flush(); + /// + /// Disposes the writer, ensuring any remaining bits are flushed to the stream. + /// + protected override void DisposeManaged() + { + if (_bitOffset > 0) + Flush(); - base.DisposeManaged(); - } + base.DisposeManaged(); + } - /// - /// Writes a single bit to the stream. - /// - /// The bit to write. - public void Write(bool bit) - { - _temp |= ((bit ? 1 : 0) << _bitOffset); + /// + /// Writes a single bit to the stream. + /// + /// The bit to write. + public void Write(bool bit) + { + _temp |= ((bit ? 1 : 0) << _bitOffset); - _bitOffset++; + _bitOffset++; - if (_bitOffset < 8) - return; + if (_bitOffset < 8) + return; - Flush(); - } + Flush(); + } - /// - /// Writes an integer value to the stream using a variable-length encoding. - /// - /// The integer value to write. - public void WriteInt(int value) + /// + /// Writes an integer value to the stream using a variable-length encoding. + /// + /// The integer value to write. + public void WriteInt(int value) + { + if (value == 0) + Write(false); + else { - if (value == 0) + Write(true); + + if (value < 0) + { + value = -value; + Write(false); + } + else + Write(true); + + if (value == 1) Write(false); else { Write(true); - if (value < 0) + if (value < 16) { - value = -value; Write(false); + WriteBits(value, 4); } - else - Write(true); - - if (value == 1) - Write(false); else { Write(true); - if (value < 16) + if (value <= byte.MaxValue) { Write(false); - WriteBits(value, 4); + WriteBits(value, 8); } else { Write(true); - if (value <= byte.MaxValue) + if (value <= ushort.MaxValue) { Write(false); - WriteBits(value, 8); + WriteBits(value, 16); } else { Write(true); - if (value <= ushort.MaxValue) + if (value <= 16777216) // 24 bits { Write(false); - WriteBits(value, 16); + WriteBits(value, 24); } else { Write(true); - - if (value <= 16777216) // 24 bits - { - Write(false); - WriteBits(value, 24); - } - else - { - Write(true); - WriteBits(value, 32); - } + WriteBits(value, 32); } } } } } } + } - /// - /// Writes a long integer value to the stream using a variable-length encoding. - /// - /// The long integer value to write. - public void WriteLong(long value) + /// + /// Writes a long integer value to the stream using a variable-length encoding. + /// + /// The long integer value to write. + public void WriteLong(long value) + { + if (value.Abs() > int.MaxValue) { - if (value.Abs() > int.MaxValue) - { - Write(true); - Write(value >= 0); - WriteBits(value.Abs(), 63); - } - else - { - Write(false); - WriteInt((int)value); - } + Write(true); + Write(value >= 0); + WriteBits(value.Abs(), 63); } - - /// - /// Writes a specified number of bits from an integer value to the stream. - /// - /// The integer value to write. - /// The number of bits to write. - public void WriteBits(int value, int bitCount) + else { - for (var i = 0; i < bitCount; i++) - Write((value & (1 << i)) != 0); + Write(false); + WriteInt((int)value); } + } - /// - /// Writes a specified number of bits from a long integer value to the stream. - /// - /// The long integer value to write. - /// The number of bits to write. - public void WriteBits(long value, int bitCount) - { - for (var i = 0; i < bitCount; i++) - Write((value & (1L << i)) != 0); - } + /// + /// Writes a specified number of bits from an integer value to the stream. + /// + /// The integer value to write. + /// The number of bits to write. + public void WriteBits(int value, int bitCount) + { + for (var i = 0; i < bitCount; i++) + Write((value & (1 << i)) != 0); + } - /// - /// Writes a decimal value to the stream. - /// - /// The decimal value to write. - public void WriteDecimal(decimal value) - { - if (value < 0) - { - value = -value; - Write(false); - } - else - Write(true); - - var bits = value.To(); + /// + /// Writes a specified number of bits from a long integer value to the stream. + /// + /// The long integer value to write. + /// The number of bits to write. + public void WriteBits(long value, int bitCount) + { + for (var i = 0; i < bitCount; i++) + Write((value & (1L << i)) != 0); + } - WriteInt(bits[0]); - WriteInt(bits[1]); - WriteInt(bits[2]); - WriteInt((bits[3] >> 16) & 0xff); + /// + /// Writes a decimal value to the stream. + /// + /// The decimal value to write. + public void WriteDecimal(decimal value) + { + if (value < 0) + { + value = -value; + Write(false); } + else + Write(true); + + var bits = value.To(); + + WriteInt(bits[0]); + WriteInt(bits[1]); + WriteInt(bits[2]); + WriteInt((bits[3] >> 16) & 0xff); } } \ No newline at end of file diff --git a/Collections/BlockingQueue.cs b/Collections/BlockingQueue.cs index 2342c758..9f5b0d88 100644 --- a/Collections/BlockingQueue.cs +++ b/Collections/BlockingQueue.cs @@ -1,45 +1,44 @@ -namespace Ecng.Collections +namespace Ecng.Collections; + +/// +/// Represents a thread-safe blocking queue implementation for storing and retrieving items of type . +/// +/// The type of elements in the queue. +public sealed class BlockingQueue : BaseBlockingQueue> { /// - /// Represents a thread-safe blocking queue implementation for storing and retrieving items of type . + /// Initializes a new instance of the class with an empty underlying queue. /// - /// The type of elements in the queue. - public sealed class BlockingQueue : BaseBlockingQueue> + public BlockingQueue() + : base(new QueueEx()) { - /// - /// Initializes a new instance of the class with an empty underlying queue. - /// - public BlockingQueue() - : base(new QueueEx()) - { - } + } - /// - /// Adds an item to the underlying queue. - /// - /// The item to enqueue. - /// If true, forces the item to be enqueued even if the queue is full; otherwise, respects the maximum size limit. - protected override void OnEnqueue(T item, bool force) - { - InnerCollection.Enqueue(item); - } + /// + /// Adds an item to the underlying queue. + /// + /// The item to enqueue. + /// If true, forces the item to be enqueued even if the queue is full; otherwise, respects the maximum size limit. + protected override void OnEnqueue(T item, bool force) + { + InnerCollection.Enqueue(item); + } - /// - /// Removes and returns the item at the head of the underlying queue. - /// - /// The dequeued item. - protected override T OnDequeue() - { - return InnerCollection.Dequeue(); - } + /// + /// Removes and returns the item at the head of the underlying queue. + /// + /// The dequeued item. + protected override T OnDequeue() + { + return InnerCollection.Dequeue(); + } - /// - /// Retrieves, but does not remove, the item at the head of the underlying queue. - /// - /// The item at the head of the queue. - protected override T OnPeek() - { - return InnerCollection.Peek(); - } + /// + /// Retrieves, but does not remove, the item at the head of the underlying queue. + /// + /// The item at the head of the queue. + protected override T OnPeek() + { + return InnerCollection.Peek(); } } \ No newline at end of file diff --git a/Collections/CachedSynchronizedDictionary.cs b/Collections/CachedSynchronizedDictionary.cs index a42e1424..d30b9424 100644 --- a/Collections/CachedSynchronizedDictionary.cs +++ b/Collections/CachedSynchronizedDictionary.cs @@ -1,180 +1,179 @@ -namespace Ecng.Collections -{ - using System.Collections.Generic; +namespace Ecng.Collections; + +using System.Collections.Generic; +/// +/// Represents a thread-safe dictionary with cached keys, values, and key-value pairs for improved performance. +/// +/// The type of keys in the dictionary. +/// The type of values in the dictionary. +public class CachedSynchronizedDictionary : SynchronizedDictionary +{ /// - /// Represents a thread-safe dictionary with cached keys, values, and key-value pairs for improved performance. + /// Initializes a new instance of the class. /// - /// The type of keys in the dictionary. - /// The type of values in the dictionary. - public class CachedSynchronizedDictionary : SynchronizedDictionary + public CachedSynchronizedDictionary() { - /// - /// Initializes a new instance of the class. - /// - public CachedSynchronizedDictionary() - { - } + } - /// - /// Initializes a new instance of the class with the specified initial capacity. - /// - /// The initial number of elements that the dictionary can contain. - public CachedSynchronizedDictionary(int capacity) - : base(capacity) - { - } + /// + /// Initializes a new instance of the class with the specified initial capacity. + /// + /// The initial number of elements that the dictionary can contain. + public CachedSynchronizedDictionary(int capacity) + : base(capacity) + { + } - /// - /// Initializes a new instance of the class with the specified equality comparer. - /// - /// The equality comparer to use when comparing keys. - public CachedSynchronizedDictionary(IEqualityComparer comparer) - : base(comparer) - { - } + /// + /// Initializes a new instance of the class with the specified equality comparer. + /// + /// The equality comparer to use when comparing keys. + public CachedSynchronizedDictionary(IEqualityComparer comparer) + : base(comparer) + { + } - /// - /// Initializes a new instance of the class with the specified initial capacity and equality comparer. - /// - /// The initial number of elements that the dictionary can contain. - /// The equality comparer to use when comparing keys. - public CachedSynchronizedDictionary(int capacity, IEqualityComparer comparer) - : base(capacity, comparer) - { - } + /// + /// Initializes a new instance of the class with the specified initial capacity and equality comparer. + /// + /// The initial number of elements that the dictionary can contain. + /// The equality comparer to use when comparing keys. + public CachedSynchronizedDictionary(int capacity, IEqualityComparer comparer) + : base(capacity, comparer) + { + } - private TKey[] _cachedKeys; + private TKey[] _cachedKeys; - /// - /// Gets an array of cached keys from the dictionary. - /// - /// - /// The keys are cached for performance and are reset when the dictionary is modified. - /// - public TKey[] CachedKeys + /// + /// Gets an array of cached keys from the dictionary. + /// + /// + /// The keys are cached for performance and are reset when the dictionary is modified. + /// + public TKey[] CachedKeys + { + get { - get - { - lock (SyncRoot) - return _cachedKeys ??= [.. Keys]; - } + lock (SyncRoot) + return _cachedKeys ??= [.. Keys]; } + } - private TValue[] _cachedValues; + private TValue[] _cachedValues; - /// - /// Gets an array of cached values from the dictionary. - /// - /// - /// The values are cached for performance and are reset when the dictionary is modified. - /// - public TValue[] CachedValues + /// + /// Gets an array of cached values from the dictionary. + /// + /// + /// The values are cached for performance and are reset when the dictionary is modified. + /// + public TValue[] CachedValues + { + get { - get - { - lock (SyncRoot) - return _cachedValues ??= [.. Values]; - } + lock (SyncRoot) + return _cachedValues ??= [.. Values]; } + } - private KeyValuePair[] _cachedPairs; + private KeyValuePair[] _cachedPairs; - /// - /// Gets an array of cached key-value pairs from the dictionary. - /// - /// - /// The key-value pairs are cached for performance and are reset when the dictionary is modified. - /// - public KeyValuePair[] CachedPairs + /// + /// Gets an array of cached key-value pairs from the dictionary. + /// + /// + /// The key-value pairs are cached for performance and are reset when the dictionary is modified. + /// + public KeyValuePair[] CachedPairs + { + get { - get - { - lock (SyncRoot) - return _cachedPairs ??= [.. this]; - } + lock (SyncRoot) + return _cachedPairs ??= [.. this]; } + } - /// - /// Gets or sets the value associated with the specified key. - /// - /// The key of the value to get or set. - /// The value associated with the specified key. - public override TValue this[TKey key] + /// + /// Gets or sets the value associated with the specified key. + /// + /// The key of the value to get or set. + /// The value associated with the specified key. + public override TValue this[TKey key] + { + set { - set + lock (SyncRoot) { - lock (SyncRoot) - { - var isKey = false; + var isKey = false; - if (_cachedKeys != null && !ContainsKey(key)) - isKey = true; + if (_cachedKeys != null && !ContainsKey(key)) + isKey = true; - OnResetCache(isKey); + OnResetCache(isKey); - base[key] = value; - } + base[key] = value; } } + } - /// - /// Adds the specified key and value to the dictionary. - /// - /// The key of the element to add. - /// The value of the element to add. - public override void Add(TKey key, TValue value) + /// + /// Adds the specified key and value to the dictionary. + /// + /// The key of the element to add. + /// The value of the element to add. + public override void Add(TKey key, TValue value) + { + lock (SyncRoot) { - lock (SyncRoot) - { - base.Add(key, value); + base.Add(key, value); - OnResetCache(true); - } + OnResetCache(true); } + } - /// - /// Removes the value with the specified key from the dictionary. - /// - /// The key of the element to remove. - /// true if the element is successfully found and removed; otherwise, false. - public override bool Remove(TKey key) + /// + /// Removes the value with the specified key from the dictionary. + /// + /// The key of the element to remove. + /// true if the element is successfully found and removed; otherwise, false. + public override bool Remove(TKey key) + { + lock (SyncRoot) { - lock (SyncRoot) + if (base.Remove(key)) { - if (base.Remove(key)) - { - OnResetCache(true); - - return true; - } + OnResetCache(true); - return false; + return true; } + + return false; } + } - /// - /// Removes all keys and values from the dictionary. - /// - public override void Clear() + /// + /// Removes all keys and values from the dictionary. + /// + public override void Clear() + { + lock (SyncRoot) { - lock (SyncRoot) - { - base.Clear(); + base.Clear(); - OnResetCache(true); - } + OnResetCache(true); } + } - /// - /// Resets the cached keys, values, and key-value pairs when the dictionary is modified. - /// - /// Indicates whether the cache reset is due to a key modification. - protected virtual void OnResetCache(bool isKey) - { - _cachedKeys = null; - _cachedValues = null; - _cachedPairs = null; - } + /// + /// Resets the cached keys, values, and key-value pairs when the dictionary is modified. + /// + /// Indicates whether the cache reset is due to a key modification. + protected virtual void OnResetCache(bool isKey) + { + _cachedKeys = null; + _cachedValues = null; + _cachedPairs = null; } } diff --git a/Collections/CachedSynchronizedList.cs b/Collections/CachedSynchronizedList.cs index 942cc291..7053421a 100644 --- a/Collections/CachedSynchronizedList.cs +++ b/Collections/CachedSynchronizedList.cs @@ -1,52 +1,51 @@ -namespace Ecng.Collections +namespace Ecng.Collections; + +/// +/// Represents a thread-safe list with a cached array of its elements for improved performance. +/// +/// The type of elements in the list. +public class CachedSynchronizedList : SynchronizedList { /// - /// Represents a thread-safe list with a cached array of its elements for improved performance. + /// Initializes a new instance of the class. /// - /// The type of elements in the list. - public class CachedSynchronizedList : SynchronizedList + public CachedSynchronizedList() { - /// - /// Initializes a new instance of the class. - /// - public CachedSynchronizedList() - { - } + } - /// - /// Initializes a new instance of the class with the specified initial capacity. - /// - /// The initial number of elements that the list can contain. - public CachedSynchronizedList(int capacity) - : base(capacity) - { - } + /// + /// Initializes a new instance of the class with the specified initial capacity. + /// + /// The initial number of elements that the list can contain. + public CachedSynchronizedList(int capacity) + : base(capacity) + { + } - private T[] _cache; + private T[] _cache; - /// - /// Gets a cached array of the elements in the list. - /// - /// - /// The array is cached for performance and is reset when the list is modified. - /// - public T[] Cache + /// + /// Gets a cached array of the elements in the list. + /// + /// + /// The array is cached for performance and is reset when the list is modified. + /// + public T[] Cache + { + get { - get - { - lock (SyncRoot) - return _cache ??= [.. this]; - } + lock (SyncRoot) + return _cache ??= [.. this]; } + } - /// - /// Called when the list is modified to reset the cached array. - /// - protected override void OnChanged() - { - _cache = null; + /// + /// Called when the list is modified to reset the cached array. + /// + protected override void OnChanged() + { + _cache = null; - base.OnChanged(); - } + base.OnChanged(); } } \ No newline at end of file diff --git a/Collections/CachedSynchronizedOrderedDictionary.cs b/Collections/CachedSynchronizedOrderedDictionary.cs index 68aaedcc..99c5b4c7 100644 --- a/Collections/CachedSynchronizedOrderedDictionary.cs +++ b/Collections/CachedSynchronizedOrderedDictionary.cs @@ -1,171 +1,170 @@ -namespace Ecng.Collections -{ - using System; - using System.Collections.Generic; +namespace Ecng.Collections; + +using System; +using System.Collections.Generic; +/// +/// Represents a thread-safe ordered dictionary with cached keys, values, and key-value pairs for improved performance. +/// +/// The type of keys in the dictionary. +/// The type of values in the dictionary. +public class CachedSynchronizedOrderedDictionary : SynchronizedOrderedDictionary +{ /// - /// Represents a thread-safe ordered dictionary with cached keys, values, and key-value pairs for improved performance. + /// Initializes a new instance of the class. /// - /// The type of keys in the dictionary. - /// The type of values in the dictionary. - public class CachedSynchronizedOrderedDictionary : SynchronizedOrderedDictionary + public CachedSynchronizedOrderedDictionary() { - /// - /// Initializes a new instance of the class. - /// - public CachedSynchronizedOrderedDictionary() - { - } + } - /// - /// Initializes a new instance of the class with the specified key comparer. - /// - /// The comparer to use when comparing keys. - public CachedSynchronizedOrderedDictionary(IComparer comparer) - : base(comparer) - { - } + /// + /// Initializes a new instance of the class with the specified key comparer. + /// + /// The comparer to use when comparing keys. + public CachedSynchronizedOrderedDictionary(IComparer comparer) + : base(comparer) + { + } - /// - /// Initializes a new instance of the class with the specified key comparison function. - /// - /// The function to use when comparing keys. - public CachedSynchronizedOrderedDictionary(Func comparer) - : base(comparer) - { - } + /// + /// Initializes a new instance of the class with the specified key comparison function. + /// + /// The function to use when comparing keys. + public CachedSynchronizedOrderedDictionary(Func comparer) + : base(comparer) + { + } - private TKey[] _cachedKeys; + private TKey[] _cachedKeys; - /// - /// Gets an array of cached keys from the dictionary. - /// - /// - /// The keys are cached for performance and are reset when the dictionary is modified. - /// - public TKey[] CachedKeys + /// + /// Gets an array of cached keys from the dictionary. + /// + /// + /// The keys are cached for performance and are reset when the dictionary is modified. + /// + public TKey[] CachedKeys + { + get { - get - { - lock (SyncRoot) - return _cachedKeys ??= [.. Keys]; - } + lock (SyncRoot) + return _cachedKeys ??= [.. Keys]; } + } - private TValue[] _cachedValues; + private TValue[] _cachedValues; - /// - /// Gets an array of cached values from the dictionary. - /// - /// - /// The values are cached for performance and are reset when the dictionary is modified. - /// - public TValue[] CachedValues + /// + /// Gets an array of cached values from the dictionary. + /// + /// + /// The values are cached for performance and are reset when the dictionary is modified. + /// + public TValue[] CachedValues + { + get { - get - { - lock (SyncRoot) - return _cachedValues ??= [.. Values]; - } + lock (SyncRoot) + return _cachedValues ??= [.. Values]; } + } - private KeyValuePair[] _cachedPairs; + private KeyValuePair[] _cachedPairs; - /// - /// Gets an array of cached key-value pairs from the dictionary. - /// - /// - /// The key-value pairs are cached for performance and are reset when the dictionary is modified. - /// - public KeyValuePair[] CachedPairs + /// + /// Gets an array of cached key-value pairs from the dictionary. + /// + /// + /// The key-value pairs are cached for performance and are reset when the dictionary is modified. + /// + public KeyValuePair[] CachedPairs + { + get { - get - { - lock (SyncRoot) - return _cachedPairs ??= [.. this]; - } + lock (SyncRoot) + return _cachedPairs ??= [.. this]; } + } - /// - /// Gets or sets the value associated with the specified key. - /// - /// The key of the value to get or set. - /// The value associated with the specified key. - public override TValue this[TKey key] + /// + /// Gets or sets the value associated with the specified key. + /// + /// The key of the value to get or set. + /// The value associated with the specified key. + public override TValue this[TKey key] + { + set { - set + lock (SyncRoot) { - lock (SyncRoot) - { - var isKey = false; + var isKey = false; - if (!ContainsKey(key)) - isKey = true; + if (!ContainsKey(key)) + isKey = true; - OnResetCache(isKey); + OnResetCache(isKey); - base[key] = value; - } + base[key] = value; } } + } - /// - /// Adds the specified key and value to the dictionary. - /// - /// The key of the element to add. - /// The value of the element to add. - public override void Add(TKey key, TValue value) + /// + /// Adds the specified key and value to the dictionary. + /// + /// The key of the element to add. + /// The value of the element to add. + public override void Add(TKey key, TValue value) + { + lock (SyncRoot) { - lock (SyncRoot) - { - base.Add(key, value); + base.Add(key, value); - OnResetCache(true); - } + OnResetCache(true); } + } - /// - /// Removes the value with the specified key from the dictionary. - /// - /// The key of the element to remove. - /// true if the element is successfully found and removed; otherwise, false. - public override bool Remove(TKey key) + /// + /// Removes the value with the specified key from the dictionary. + /// + /// The key of the element to remove. + /// true if the element is successfully found and removed; otherwise, false. + public override bool Remove(TKey key) + { + lock (SyncRoot) { - lock (SyncRoot) + if (base.Remove(key)) { - if (base.Remove(key)) - { - OnResetCache(true); - - return true; - } + OnResetCache(true); - return false; + return true; } + + return false; } + } - /// - /// Removes all keys and values from the dictionary. - /// - public override void Clear() + /// + /// Removes all keys and values from the dictionary. + /// + public override void Clear() + { + lock (SyncRoot) { - lock (SyncRoot) - { - base.Clear(); + base.Clear(); - OnResetCache(true); - } + OnResetCache(true); } + } - /// - /// Resets the cached keys, values, and key-value pairs when the dictionary is modified. - /// - /// Indicates whether the cache reset is due to a key modification. - protected virtual void OnResetCache(bool isKey) - { - _cachedKeys = null; - _cachedValues = null; - _cachedPairs = null; - } + /// + /// Resets the cached keys, values, and key-value pairs when the dictionary is modified. + /// + /// Indicates whether the cache reset is due to a key modification. + protected virtual void OnResetCache(bool isKey) + { + _cachedKeys = null; + _cachedValues = null; + _cachedPairs = null; } } \ No newline at end of file diff --git a/Collections/CachedSynchronizedPairSet.cs b/Collections/CachedSynchronizedPairSet.cs index 10d08dfc..79f3c2a0 100644 --- a/Collections/CachedSynchronizedPairSet.cs +++ b/Collections/CachedSynchronizedPairSet.cs @@ -1,171 +1,170 @@ -namespace Ecng.Collections -{ - using System.Collections.Generic; +namespace Ecng.Collections; + +using System.Collections.Generic; +/// +/// Represents a thread-safe set of key-value pairs with cached keys, values, and pairs for improved performance. +/// +/// The type of keys in the set. +/// The type of values in the set. +public class CachedSynchronizedPairSet : SynchronizedPairSet +{ /// - /// Represents a thread-safe set of key-value pairs with cached keys, values, and pairs for improved performance. + /// Initializes a new instance of the class. /// - /// The type of keys in the set. - /// The type of values in the set. - public class CachedSynchronizedPairSet : SynchronizedPairSet + public CachedSynchronizedPairSet() { - /// - /// Initializes a new instance of the class. - /// - public CachedSynchronizedPairSet() - { - } + } - /// - /// Initializes a new instance of the class with the specified key comparer. - /// - /// The equality comparer to use when comparing keys. - public CachedSynchronizedPairSet(IEqualityComparer comparer) - : base(comparer) - { - } + /// + /// Initializes a new instance of the class with the specified key comparer. + /// + /// The equality comparer to use when comparing keys. + public CachedSynchronizedPairSet(IEqualityComparer comparer) + : base(comparer) + { + } - /// - /// Initializes a new instance of the class with the specified key and value comparers. - /// - /// The equality comparer to use when comparing keys. - /// The equality comparer to use when comparing values. - public CachedSynchronizedPairSet(IEqualityComparer keyComparer, IEqualityComparer valueComparer) - : base(keyComparer, valueComparer) - { - } + /// + /// Initializes a new instance of the class with the specified key and value comparers. + /// + /// The equality comparer to use when comparing keys. + /// The equality comparer to use when comparing values. + public CachedSynchronizedPairSet(IEqualityComparer keyComparer, IEqualityComparer valueComparer) + : base(keyComparer, valueComparer) + { + } - private TKey[] _cachedKeys; + private TKey[] _cachedKeys; - /// - /// Gets an array of cached keys from the set. - /// - /// - /// The keys are cached for performance and are reset when the set is modified. - /// - public TKey[] CachedKeys + /// + /// Gets an array of cached keys from the set. + /// + /// + /// The keys are cached for performance and are reset when the set is modified. + /// + public TKey[] CachedKeys + { + get { - get - { - lock (SyncRoot) - return _cachedKeys ??= [.. Keys]; - } + lock (SyncRoot) + return _cachedKeys ??= [.. Keys]; } + } - private TValue[] _cachedValues; + private TValue[] _cachedValues; - /// - /// Gets an array of cached values from the set. - /// - /// - /// The values are cached for performance and are reset when the set is modified. - /// - public TValue[] CachedValues + /// + /// Gets an array of cached values from the set. + /// + /// + /// The values are cached for performance and are reset when the set is modified. + /// + public TValue[] CachedValues + { + get { - get - { - lock (SyncRoot) - return _cachedValues ??= [.. Values]; - } + lock (SyncRoot) + return _cachedValues ??= [.. Values]; } + } - private KeyValuePair[] _cachedPairs; + private KeyValuePair[] _cachedPairs; - /// - /// Gets an array of cached key-value pairs from the set. - /// - /// - /// The key-value pairs are cached for performance and are reset when the set is modified. - /// - public KeyValuePair[] CachedPairs + /// + /// Gets an array of cached key-value pairs from the set. + /// + /// + /// The key-value pairs are cached for performance and are reset when the set is modified. + /// + public KeyValuePair[] CachedPairs + { + get { - get - { - lock (SyncRoot) - return _cachedPairs ??= [.. this]; - } + lock (SyncRoot) + return _cachedPairs ??= [.. this]; } + } - /// - /// Gets or sets the value associated with the specified key. - /// - /// The key of the value to get or set. - /// The value associated with the specified key. - public override TValue this[TKey key] + /// + /// Gets or sets the value associated with the specified key. + /// + /// The key of the value to get or set. + /// The value associated with the specified key. + public override TValue this[TKey key] + { + set { - set + lock (SyncRoot) { - lock (SyncRoot) - { - var isKey = false; + var isKey = false; - if (_cachedKeys != null && !ContainsKey(key)) - isKey = true; + if (_cachedKeys != null && !ContainsKey(key)) + isKey = true; - OnResetCache(isKey); + OnResetCache(isKey); - base[key] = value; - } + base[key] = value; } } + } - /// - /// Adds the specified key and value to the set. - /// - /// The key of the element to add. - /// The value of the element to add. - public override void Add(TKey key, TValue value) + /// + /// Adds the specified key and value to the set. + /// + /// The key of the element to add. + /// The value of the element to add. + public override void Add(TKey key, TValue value) + { + lock (SyncRoot) { - lock (SyncRoot) - { - base.Add(key, value); + base.Add(key, value); - OnResetCache(true); - } + OnResetCache(true); } + } - /// - /// Removes the value with the specified key from the set. - /// - /// The key of the element to remove. - /// true if the element is successfully found and removed; otherwise, false. - public override bool Remove(TKey key) + /// + /// Removes the value with the specified key from the set. + /// + /// The key of the element to remove. + /// true if the element is successfully found and removed; otherwise, false. + public override bool Remove(TKey key) + { + lock (SyncRoot) { - lock (SyncRoot) + if (base.Remove(key)) { - if (base.Remove(key)) - { - OnResetCache(true); - - return true; - } + OnResetCache(true); - return false; + return true; } + + return false; } + } - /// - /// Removes all keys and values from the set. - /// - public override void Clear() + /// + /// Removes all keys and values from the set. + /// + public override void Clear() + { + lock (SyncRoot) { - lock (SyncRoot) - { - base.Clear(); + base.Clear(); - OnResetCache(true); - } + OnResetCache(true); } + } - /// - /// Resets the cached keys, values, and key-value pairs when the set is modified. - /// - /// Indicates whether the cache reset is due to a key modification. - protected virtual void OnResetCache(bool isKey) - { - _cachedKeys = null; - _cachedValues = null; - _cachedPairs = null; - } + /// + /// Resets the cached keys, values, and key-value pairs when the set is modified. + /// + /// Indicates whether the cache reset is due to a key modification. + protected virtual void OnResetCache(bool isKey) + { + _cachedKeys = null; + _cachedValues = null; + _cachedPairs = null; } } \ No newline at end of file diff --git a/Collections/CachedSynchronizedSet.cs b/Collections/CachedSynchronizedSet.cs index 76fc14d8..1098a9be 100644 --- a/Collections/CachedSynchronizedSet.cs +++ b/Collections/CachedSynchronizedSet.cs @@ -1,103 +1,102 @@ -namespace Ecng.Collections -{ - using System.Collections.Generic; +namespace Ecng.Collections; + +using System.Collections.Generic; +/// +/// Represents a thread-safe set with a cached array of its elements for improved performance. +/// +/// The type of elements in the set. +public class CachedSynchronizedSet : SynchronizedSet +{ /// - /// Represents a thread-safe set with a cached array of its elements for improved performance. + /// Initializes a new instance of the class. /// - /// The type of elements in the set. - public class CachedSynchronizedSet : SynchronizedSet + public CachedSynchronizedSet() { - /// - /// Initializes a new instance of the class. - /// - public CachedSynchronizedSet() - { - } + } - /// - /// Initializes a new instance of the class with the specified indexing option. - /// - /// A value indicating whether indexing is allowed. - public CachedSynchronizedSet(bool allowIndexing) - : base(allowIndexing) - { - } + /// + /// Initializes a new instance of the class with the specified indexing option. + /// + /// A value indicating whether indexing is allowed. + public CachedSynchronizedSet(bool allowIndexing) + : base(allowIndexing) + { + } - /// - /// Initializes a new instance of the class with the specified collection. - /// - /// The collection whose elements are copied to the new set. - public CachedSynchronizedSet(IEnumerable collection) - : base(collection) - { - } + /// + /// Initializes a new instance of the class with the specified collection. + /// + /// The collection whose elements are copied to the new set. + public CachedSynchronizedSet(IEnumerable collection) + : base(collection) + { + } - /// - /// Initializes a new instance of the class with the specified equality comparer. - /// - /// The equality comparer to use when comparing elements. - public CachedSynchronizedSet(IEqualityComparer comparer) - : base(comparer) - { - } + /// + /// Initializes a new instance of the class with the specified equality comparer. + /// + /// The equality comparer to use when comparing elements. + public CachedSynchronizedSet(IEqualityComparer comparer) + : base(comparer) + { + } - /// - /// Initializes a new instance of the class with the specified collection and equality comparer. - /// - /// The collection whose elements are copied to the new set. - /// The equality comparer to use when comparing elements. - public CachedSynchronizedSet(IEnumerable collection, IEqualityComparer comparer) - : base(collection, comparer) - { - } + /// + /// Initializes a new instance of the class with the specified collection and equality comparer. + /// + /// The collection whose elements are copied to the new set. + /// The equality comparer to use when comparing elements. + public CachedSynchronizedSet(IEnumerable collection, IEqualityComparer comparer) + : base(collection, comparer) + { + } - /// - /// Initializes a new instance of the class with the specified indexing option and equality comparer. - /// - /// A value indicating whether indexing is allowed. - /// The equality comparer to use when comparing elements. - public CachedSynchronizedSet(bool allowIndexing, IEqualityComparer comparer) - : base(allowIndexing, comparer) - { - } + /// + /// Initializes a new instance of the class with the specified indexing option and equality comparer. + /// + /// A value indicating whether indexing is allowed. + /// The equality comparer to use when comparing elements. + public CachedSynchronizedSet(bool allowIndexing, IEqualityComparer comparer) + : base(allowIndexing, comparer) + { + } - /// - /// Initializes a new instance of the class with the specified indexing option, collection, and equality comparer. - /// - /// A value indicating whether indexing is allowed. - /// The collection whose elements are copied to the new set. - /// The equality comparer to use when comparing elements. - public CachedSynchronizedSet(bool allowIndexing, IEnumerable collection, IEqualityComparer comparer) - : base(allowIndexing, collection, comparer) - { - } + /// + /// Initializes a new instance of the class with the specified indexing option, collection, and equality comparer. + /// + /// A value indicating whether indexing is allowed. + /// The collection whose elements are copied to the new set. + /// The equality comparer to use when comparing elements. + public CachedSynchronizedSet(bool allowIndexing, IEnumerable collection, IEqualityComparer comparer) + : base(allowIndexing, collection, comparer) + { + } - private T[] _cache; + private T[] _cache; - /// - /// Gets a cached array of the elements in the set. - /// - /// - /// The array is cached for performance and is reset when the set is modified. - /// - public T[] Cache + /// + /// Gets a cached array of the elements in the set. + /// + /// + /// The array is cached for performance and is reset when the set is modified. + /// + public T[] Cache + { + get { - get - { - lock (SyncRoot) - return _cache ??= [.. this]; - } + lock (SyncRoot) + return _cache ??= [.. this]; } + } - /// - /// Called when the set is modified to reset the cached array. - /// - protected override void OnChanged() - { - _cache = null; + /// + /// Called when the set is modified to reset the cached array. + /// + protected override void OnChanged() + { + _cache = null; - base.OnChanged(); - } + base.OnChanged(); } } \ No newline at end of file diff --git a/Collections/CircularBuffer.cs b/Collections/CircularBuffer.cs index fd04e5ac..d68546ad 100644 --- a/Collections/CircularBuffer.cs +++ b/Collections/CircularBuffer.cs @@ -1,429 +1,428 @@ +namespace Ecng.Collections; + // Source: https://github.com/joaoportela/CircularBuffer-CSharp using System; using System.Collections; using System.Collections.Generic; -namespace Ecng.Collections +/// +/// Circular buffer. +/// +/// When writing to a full buffer: +/// PushBack -> removes this[0] / Front() +/// PushFront -> removes this[Size-1] / Back() +/// +/// this implementation is inspired by +/// http://www.boost.org/doc/libs/1_53_0/libs/circular_buffer/doc/circular_buffer.html +/// because I liked their interface. +/// +public class CircularBuffer : IEnumerable, IList { + private T[] _buffer; + + /// + /// The _start. Index of the first element in buffer. + /// + private int _start; + + /// + /// The _end. Index after the last element in the buffer. + /// + private int _end; + + /// + /// The _size. Buffer size. + /// + private int _count; + /// - /// Circular buffer. + /// Initializes a new instance of the class. /// - /// When writing to a full buffer: - /// PushBack -> removes this[0] / Front() - /// PushFront -> removes this[Size-1] / Back() + /// + /// + /// Buffer capacity. Must be positive. + /// + public CircularBuffer(int capacity) + : this(capacity, []) + { + } + + /// + /// Initializes a new instance of the class. /// - /// this implementation is inspired by - /// http://www.boost.org/doc/libs/1_53_0/libs/circular_buffer/doc/circular_buffer.html - /// because I liked their interface. /// - public class CircularBuffer : IEnumerable, IList + /// + /// Buffer capacity. Must be positive. + /// + /// + /// Items to fill buffer with. Items length must be less than capacity. + /// Suggestion: use Skip(x).Take(y).ToArray() to build this argument from + /// any enumerable. + /// + public CircularBuffer(int capacity, T[] items) { - private T[] _buffer; - - /// - /// The _start. Index of the first element in buffer. - /// - private int _start; - - /// - /// The _end. Index after the last element in the buffer. - /// - private int _end; - - /// - /// The _size. Buffer size. - /// - private int _count; - - /// - /// Initializes a new instance of the class. - /// - /// - /// - /// Buffer capacity. Must be positive. - /// - public CircularBuffer(int capacity) - : this(capacity, []) + if (capacity < 1) { + throw new ArgumentException( + "Circular buffer cannot have negative or zero capacity.", nameof(capacity)); } - - /// - /// Initializes a new instance of the class. - /// - /// - /// - /// Buffer capacity. Must be positive. - /// - /// - /// Items to fill buffer with. Items length must be less than capacity. - /// Suggestion: use Skip(x).Take(y).ToArray() to build this argument from - /// any enumerable. - /// - public CircularBuffer(int capacity, T[] items) + if (items == null) { - if (capacity < 1) - { - throw new ArgumentException( - "Circular buffer cannot have negative or zero capacity.", nameof(capacity)); - } - if (items == null) - { - throw new ArgumentNullException(nameof(items)); - } - if (items.Length > capacity) - { - throw new ArgumentException( - "Too many items to fit circular buffer", nameof(items)); - } + throw new ArgumentNullException(nameof(items)); + } + if (items.Length > capacity) + { + throw new ArgumentException( + "Too many items to fit circular buffer", nameof(items)); + } - _buffer = new T[capacity]; + _buffer = new T[capacity]; - Array.Copy(items, _buffer, items.Length); - _count = items.Length; + Array.Copy(items, _buffer, items.Length); + _count = items.Length; - _start = 0; - _end = _count == capacity ? 0 : _count; - } + _start = 0; + _end = _count == capacity ? 0 : _count; + } - /// - /// Maximum capacity of the buffer. Elements pushed into the buffer after - /// maximum capacity is reached (IsFull = true), will remove an element. - /// - public virtual int Capacity + /// + /// Maximum capacity of the buffer. Elements pushed into the buffer after + /// maximum capacity is reached (IsFull = true), will remove an element. + /// + public virtual int Capacity + { + get => _buffer.Length; + set { - get => _buffer.Length; - set - { - Array.Resize(ref _buffer, value); - } + Array.Resize(ref _buffer, value); } + } - /// - /// Boolean indicating if Circular is at full capacity. - /// Adding more elements when the buffer is full will - /// cause elements to be removed from the other end - /// of the buffer. - /// - public bool IsFull + /// + /// Boolean indicating if Circular is at full capacity. + /// Adding more elements when the buffer is full will + /// cause elements to be removed from the other end + /// of the buffer. + /// + public bool IsFull + { + get { - get - { - return Count == Capacity; - } + return Count == Capacity; } + } - /// - /// True if has no elements. - /// - public bool IsEmpty => Count == 0; - - /// - /// Current buffer size (the number of elements that the buffer has). - /// - public int Count => _count; - - /// - /// Element at the front of the buffer - this[0]. - /// - /// The value of the element of type T at the front of the buffer. - public T Front() - { - ThrowIfEmpty(); - return _buffer[_start]; - } + /// + /// True if has no elements. + /// + public bool IsEmpty => Count == 0; - /// - /// Element at the back of the buffer - this[Size - 1]. - /// - /// The value of the element of type T at the back of the buffer. - public T Back() - { - ThrowIfEmpty(); - return _buffer[(_end != 0 ? _end : Capacity) - 1]; - } + /// + /// Current buffer size (the number of elements that the buffer has). + /// + public int Count => _count; + + /// + /// Element at the front of the buffer - this[0]. + /// + /// The value of the element of type T at the front of the buffer. + public T Front() + { + ThrowIfEmpty(); + return _buffer[_start]; + } - /// - /// Index access to elements in buffer. - /// Index does not loop around like when adding elements, - /// valid interval is [0;Size[ - /// - /// Index of element to access. - /// Thrown when index is outside of [; Size[ interval. - public T this[int index] + /// + /// Element at the back of the buffer - this[Size - 1]. + /// + /// The value of the element of type T at the back of the buffer. + public T Back() + { + ThrowIfEmpty(); + return _buffer[(_end != 0 ? _end : Capacity) - 1]; + } + + /// + /// Index access to elements in buffer. + /// Index does not loop around like when adding elements, + /// valid interval is [0;Size[ + /// + /// Index of element to access. + /// Thrown when index is outside of [; Size[ interval. + public T this[int index] + { + get { - get + if (IsEmpty) { - if (IsEmpty) - { - throw new IndexOutOfRangeException(string.Format("Cannot access index {0}. Buffer is empty", index)); - } - if (index >= _count) - { - throw new IndexOutOfRangeException(string.Format("Cannot access index {0}. Buffer size is {1}", index, _count)); - } - int actualIndex = InternalIndex(index); - return _buffer[actualIndex]; + throw new IndexOutOfRangeException(string.Format("Cannot access index {0}. Buffer is empty", index)); } - set + if (index >= _count) { - if (IsEmpty) - { - throw new IndexOutOfRangeException(string.Format("Cannot access index {0}. Buffer is empty", index)); - } - if (index >= _count) - { - throw new IndexOutOfRangeException(string.Format("Cannot access index {0}. Buffer size is {1}", index, _count)); - } - int actualIndex = InternalIndex(index); - _buffer[actualIndex] = value; + throw new IndexOutOfRangeException(string.Format("Cannot access index {0}. Buffer size is {1}", index, _count)); } + int actualIndex = InternalIndex(index); + return _buffer[actualIndex]; } - - /// - /// Pushes a new element to the back of the buffer. Back()/this[Size-1] - /// will now return this element. - /// - /// When the buffer is full, the element at Front()/this[0] will be - /// popped to allow for this new element to fit. - /// - /// Item to push to the back of the buffer - public virtual void PushBack(T item) + set { - if (IsFull) + if (IsEmpty) { - _buffer[_end] = item; - Increment(ref _end); - _start = _end; + throw new IndexOutOfRangeException(string.Format("Cannot access index {0}. Buffer is empty", index)); } - else + if (index >= _count) { - _buffer[_end] = item; - Increment(ref _end); - ++_count; + throw new IndexOutOfRangeException(string.Format("Cannot access index {0}. Buffer size is {1}", index, _count)); } + int actualIndex = InternalIndex(index); + _buffer[actualIndex] = value; } + } - /// - /// Pushes a new element to the front of the buffer. Front()/this[0] - /// will now return this element. - /// - /// When the buffer is full, the element at Back()/this[Size-1] will be - /// popped to allow for this new element to fit. - /// - /// Item to push to the front of the buffer - public void PushFront(T item) + /// + /// Pushes a new element to the back of the buffer. Back()/this[Size-1] + /// will now return this element. + /// + /// When the buffer is full, the element at Front()/this[0] will be + /// popped to allow for this new element to fit. + /// + /// Item to push to the back of the buffer + public virtual void PushBack(T item) + { + if (IsFull) { - if (IsFull) - { - Decrement(ref _start); - _end = _start; - _buffer[_start] = item; - } - else - { - Decrement(ref _start); - _buffer[_start] = item; - ++_count; - } + _buffer[_end] = item; + Increment(ref _end); + _start = _end; } - - /// - /// Removes the element at the back of the buffer. Decreasing the - /// Buffer size by 1. - /// - public void PopBack() + else { - ThrowIfEmpty("Cannot take elements from an empty buffer."); - Decrement(ref _end); - _buffer[_end] = default; - --_count; + _buffer[_end] = item; + Increment(ref _end); + ++_count; } + } - /// - /// Removes the element at the front of the buffer. Decreasing the - /// Buffer size by 1. - /// - public void PopFront() + /// + /// Pushes a new element to the front of the buffer. Front()/this[0] + /// will now return this element. + /// + /// When the buffer is full, the element at Back()/this[Size-1] will be + /// popped to allow for this new element to fit. + /// + /// Item to push to the front of the buffer + public void PushFront(T item) + { + if (IsFull) + { + Decrement(ref _start); + _end = _start; + _buffer[_start] = item; + } + else { - ThrowIfEmpty("Cannot take elements from an empty buffer."); - _buffer[_start] = default; - Increment(ref _start); - --_count; + Decrement(ref _start); + _buffer[_start] = item; + ++_count; } + } - /// - /// Clears the contents of the array. Size = 0, Capacity is unchanged. - /// - /// - public virtual void Clear() + /// + /// Removes the element at the back of the buffer. Decreasing the + /// Buffer size by 1. + /// + public void PopBack() + { + ThrowIfEmpty("Cannot take elements from an empty buffer."); + Decrement(ref _end); + _buffer[_end] = default; + --_count; + } + + /// + /// Removes the element at the front of the buffer. Decreasing the + /// Buffer size by 1. + /// + public void PopFront() + { + ThrowIfEmpty("Cannot take elements from an empty buffer."); + _buffer[_start] = default; + Increment(ref _start); + --_count; + } + + /// + /// Clears the contents of the array. Size = 0, Capacity is unchanged. + /// + /// + public virtual void Clear() + { + // to clear we just reset everything. + _start = 0; + _end = 0; + _count = 0; + Array.Clear(_buffer, 0, _buffer.Length); + } + + /// + /// Copies the buffer contents to an array, according to the logical + /// contents of the buffer (i.e. independent of the internal + /// order/contents) + /// + /// A new array with a copy of the buffer contents. + public T[] ToArray() + { + T[] newArray = new T[Count]; + int newArrayOffset = 0; + var segments = ToArraySegments(); + foreach (var segment in segments) { - // to clear we just reset everything. - _start = 0; - _end = 0; - _count = 0; - Array.Clear(_buffer, 0, _buffer.Length); + Array.Copy(segment.Array, segment.Offset, newArray, newArrayOffset, segment.Count); + newArrayOffset += segment.Count; } + return newArray; + } - /// - /// Copies the buffer contents to an array, according to the logical - /// contents of the buffer (i.e. independent of the internal - /// order/contents) - /// - /// A new array with a copy of the buffer contents. - public T[] ToArray() + /// + /// Get the contents of the buffer as 2 ArraySegments. + /// Respects the logical contents of the buffer, where + /// each segment and items in each segment are ordered + /// according to insertion. + /// + /// Fast: does not copy the array elements. + /// Useful for methods like Send(IList<ArraySegment<Byte>>). + /// + /// Segments may be empty. + /// + /// An IList with 2 segments corresponding to the buffer content. + public IList> ToArraySegments() + { + return [ArrayOne(), ArrayTwo()]; + } + + #region IEnumerable implementation + /// + /// Returns an enumerator that iterates through this buffer. + /// + /// An enumerator that can be used to iterate this collection. + public IEnumerator GetEnumerator() + { + var segments = ToArraySegments(); + foreach (var segment in segments) { - T[] newArray = new T[Count]; - int newArrayOffset = 0; - var segments = ToArraySegments(); - foreach (var segment in segments) + for (int i = 0; i < segment.Count; i++) { - Array.Copy(segment.Array, segment.Offset, newArray, newArrayOffset, segment.Count); - newArrayOffset += segment.Count; + yield return segment.Array[segment.Offset + i]; } - return newArray; } + } + #endregion + #region IEnumerable implementation + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + #endregion - /// - /// Get the contents of the buffer as 2 ArraySegments. - /// Respects the logical contents of the buffer, where - /// each segment and items in each segment are ordered - /// according to insertion. - /// - /// Fast: does not copy the array elements. - /// Useful for methods like Send(IList<ArraySegment<Byte>>). - /// - /// Segments may be empty. - /// - /// An IList with 2 segments corresponding to the buffer content. - public IList> ToArraySegments() + private void ThrowIfEmpty(string message = "Cannot access an empty buffer.") + { + if (IsEmpty) { - return [ArrayOne(), ArrayTwo()]; + throw new InvalidOperationException(message); } + } - #region IEnumerable implementation - /// - /// Returns an enumerator that iterates through this buffer. - /// - /// An enumerator that can be used to iterate this collection. - public IEnumerator GetEnumerator() + /// + /// Increments the provided index variable by one, wrapping + /// around if necessary. + /// + /// + private void Increment(ref int index) + { + if (++index == Capacity) { - var segments = ToArraySegments(); - foreach (var segment in segments) - { - for (int i = 0; i < segment.Count; i++) - { - yield return segment.Array[segment.Offset + i]; - } - } + index = 0; } - #endregion - #region IEnumerable implementation - IEnumerator IEnumerable.GetEnumerator() + } + + /// + /// Decrements the provided index variable by one, wrapping + /// around if necessary. + /// + /// + private void Decrement(ref int index) + { + if (index == 0) { - return GetEnumerator(); + index = Capacity; } - #endregion + index--; + } - private void ThrowIfEmpty(string message = "Cannot access an empty buffer.") + /// + /// Converts the index in the argument to an index in _buffer + /// + /// + /// The transformed index. + /// + /// + /// External index. + /// + private int InternalIndex(int index) + { + return _start + (index < (Capacity - _start) ? index : index - Capacity); + } + + // doing ArrayOne and ArrayTwo methods returning ArraySegment as seen here: + // http://www.boost.org/doc/libs/1_37_0/libs/circular_buffer/doc/circular_buffer.html#classboost_1_1circular__buffer_1957cccdcb0c4ef7d80a34a990065818d + // http://www.boost.org/doc/libs/1_37_0/libs/circular_buffer/doc/circular_buffer.html#classboost_1_1circular__buffer_1f5081a54afbc2dfc1a7fb20329df7d5b + // should help a lot with the code. + + #region Array items easy access. + // The array is composed by at most two non-contiguous segments, + // the next two methods allow easy access to those. + + private ArraySegment ArrayOne() + { + if (IsEmpty) { - if (IsEmpty) - { - throw new InvalidOperationException(message); - } + return new([]); } - - /// - /// Increments the provided index variable by one, wrapping - /// around if necessary. - /// - /// - private void Increment(ref int index) + else if (_start < _end) { - if (++index == Capacity) - { - index = 0; - } + return new(_buffer, _start, _end - _start); } - - /// - /// Decrements the provided index variable by one, wrapping - /// around if necessary. - /// - /// - private void Decrement(ref int index) + else { - if (index == 0) - { - index = Capacity; - } - index--; + return new(_buffer, _start, _buffer.Length - _start); } + } - /// - /// Converts the index in the argument to an index in _buffer - /// - /// - /// The transformed index. - /// - /// - /// External index. - /// - private int InternalIndex(int index) + private ArraySegment ArrayTwo() + { + if (IsEmpty) { - return _start + (index < (Capacity - _start) ? index : index - Capacity); + return new([]); } - - // doing ArrayOne and ArrayTwo methods returning ArraySegment as seen here: - // http://www.boost.org/doc/libs/1_37_0/libs/circular_buffer/doc/circular_buffer.html#classboost_1_1circular__buffer_1957cccdcb0c4ef7d80a34a990065818d - // http://www.boost.org/doc/libs/1_37_0/libs/circular_buffer/doc/circular_buffer.html#classboost_1_1circular__buffer_1f5081a54afbc2dfc1a7fb20329df7d5b - // should help a lot with the code. - - #region Array items easy access. - // The array is composed by at most two non-contiguous segments, - // the next two methods allow easy access to those. - - private ArraySegment ArrayOne() + else if (_start < _end) { - if (IsEmpty) - { - return new([]); - } - else if (_start < _end) - { - return new(_buffer, _start, _end - _start); - } - else - { - return new(_buffer, _start, _buffer.Length - _start); - } + return new(_buffer, _end, 0); } - - private ArraySegment ArrayTwo() + else { - if (IsEmpty) - { - return new([]); - } - else if (_start < _end) - { - return new(_buffer, _end, 0); - } - else - { - return new(_buffer, 0, _end); - } + return new(_buffer, 0, _end); } + } - #endregion + #endregion - int IList.IndexOf(T item) => throw new NotSupportedException(); - void IList.Insert(int index, T item) => throw new NotSupportedException(); - void IList.RemoveAt(int index) => throw new NotSupportedException(); - bool ICollection.IsReadOnly => false; - void ICollection.Add(T item) => PushBack(item); - bool ICollection.Contains(T item) => throw new NotSupportedException(); - void ICollection.CopyTo(T[] array, int arrayIndex) => throw new NotSupportedException(); - bool ICollection.Remove(T item) => throw new NotSupportedException(); - } + int IList.IndexOf(T item) => throw new NotSupportedException(); + void IList.Insert(int index, T item) => throw new NotSupportedException(); + void IList.RemoveAt(int index) => throw new NotSupportedException(); + bool ICollection.IsReadOnly => false; + void ICollection.Add(T item) => PushBack(item); + bool ICollection.Contains(T item) => throw new NotSupportedException(); + void ICollection.CopyTo(T[] array, int arrayIndex) => throw new NotSupportedException(); + bool ICollection.Remove(T item) => throw new NotSupportedException(); } \ No newline at end of file diff --git a/Collections/CollectionHelper.cs b/Collections/CollectionHelper.cs index 4578f1ae..484a3c12 100644 --- a/Collections/CollectionHelper.cs +++ b/Collections/CollectionHelper.cs @@ -1,2330 +1,2329 @@ -namespace Ecng.Collections -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Linq; - using System.Threading; - using System.Threading.Tasks; +namespace Ecng.Collections; + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; - using Nito.AsyncEx; +using Nito.AsyncEx; - using Ecng.Common; +using Ecng.Common; +/// +/// Provides extension methods and helper utilities for working with collections. +/// +public static class CollectionHelper +{ /// - /// Provides extension methods and helper utilities for working with collections. + /// A private implementation of using a custom equality comparison function. /// - public static class CollectionHelper + /// The type of objects to compare. + private sealed class EqualityComparer(Func comparer) : IEqualityComparer { + private readonly Func _comparer = comparer ?? throw new ArgumentNullException(nameof(comparer)); + /// - /// A private implementation of using a custom equality comparison function. + /// Determines whether two objects are equal using the provided comparison function. /// - /// The type of objects to compare. - private sealed class EqualityComparer(Func comparer) : IEqualityComparer - { - private readonly Func _comparer = comparer ?? throw new ArgumentNullException(nameof(comparer)); - - /// - /// Determines whether two objects are equal using the provided comparison function. - /// - public bool Equals(T x, T y) => _comparer(x, y); - - /// - /// Returns a hash code for the specified object. - /// - public int GetHashCode(T obj) => obj.GetHashCode(); - } + public bool Equals(T x, T y) => _comparer(x, y); /// - /// A private implementation of using a custom comparison function. + /// Returns a hash code for the specified object. /// - /// The type of objects to compare. - private sealed class Comparer(Func comparer) : IComparer - { - private readonly Func _comparer = comparer ?? throw new ArgumentNullException(nameof(comparer)); + public int GetHashCode(T obj) => obj.GetHashCode(); + } - /// - /// Compares two objects and returns a value indicating their relative order. - /// - public int Compare(T x, T y) => _comparer(x, y); - } + /// + /// A private implementation of using a custom comparison function. + /// + /// The type of objects to compare. + private sealed class Comparer(Func comparer) : IComparer + { + private readonly Func _comparer = comparer ?? throw new ArgumentNullException(nameof(comparer)); /// - /// Converts a function to an instance. + /// Compares two objects and returns a value indicating their relative order. /// - /// The type of objects to compare. - /// The function to determine equality between two objects. - /// An instance. - public static IEqualityComparer ToComparer(this Func comparer) + public int Compare(T x, T y) => _comparer(x, y); + } + + /// + /// Converts a function to an instance. + /// + /// The type of objects to compare. + /// The function to determine equality between two objects. + /// An instance. + public static IEqualityComparer ToComparer(this Func comparer) + { + return new EqualityComparer(comparer); + } + + /// + /// Converts a function to an instance. + /// + /// The type of objects to compare. + /// The function to compare two objects. + /// An instance. + public static IComparer ToComparer(this Func comparer) + { + return new Comparer(comparer); + } + + /// + /// Converts a delegate to an instance. + /// + /// The type of objects to compare. + /// The comparison delegate. + /// An instance. + public static IComparer ToComparer(this Comparison comparer) + { + return comparer.ToFunc().ToComparer(); + } + + /// + /// Converts a delegate to a function. + /// + /// The type of objects to compare. + /// The comparison delegate. + /// A function that compares two objects. + public static Func ToFunc(this Comparison comparer) + { + return (t1, t2) => comparer(t1, t2); + } + + /// + /// Determines whether two sequences are equal using a custom equality comparer function. + /// + /// The type of elements in the sequences. + /// The first sequence to compare. + /// The second sequence to compare. + /// The function to determine equality between elements. + /// True if the sequences are equal; otherwise, false. + public static bool SequenceEqual(this IEnumerable first, IEnumerable second, Func comparer) + { + return first.SequenceEqual(second, comparer.ToComparer()); + } + + /// + /// Sorts a collection using a delegate. + /// + /// The type of elements in the collection. + /// The collection to sort. + /// The comparison delegate to determine order. + /// An ordered enumerable of the collection. + public static IOrderedEnumerable OrderBy(this IEnumerable collection, Comparison comparison) + { + return collection.OrderBy(item => item, comparison.ToComparer()); + } + + /// + /// Returns the index of the first element in a sequence that satisfies a specified condition. + /// + /// The type of elements in the sequence. + /// The sequence to search. + /// The condition to test each element against. + /// The zero-based index of the first matching element, or -1 if no element is found. + public static int IndexOf(this IEnumerable source, Func predicate) + { + var index = 0; + + foreach (var t in source) { - return new EqualityComparer(comparer); + if (predicate(t)) + return index; + + index++; } - /// - /// Converts a function to an instance. - /// - /// The type of objects to compare. - /// The function to compare two objects. - /// An instance. - public static IComparer ToComparer(this Func comparer) + return -1; + } + + /// + /// Adds a range of values to a collection, ensuring no duplicates if the collection already contains them. + /// + /// The type of elements in the collection. + /// The collection to add values to. + /// The values to add. + /// Thrown when is null. + public static void TryAdd(this ICollection collection, IEnumerable values) + { + if (values is null) + throw new ArgumentNullException(nameof(values)); + + void InternalTryAdd() { - return new Comparer(comparer); + foreach (var value in values) + { + if (!collection.Contains(value)) + collection.Add(value); + } } - /// - /// Converts a delegate to an instance. - /// - /// The type of objects to compare. - /// The comparison delegate. - /// An instance. - public static IComparer ToComparer(this Comparison comparer) + if (collection is ISynchronizedCollection sync) { - return comparer.ToFunc().ToComparer(); + lock (sync.SyncRoot) + InternalTryAdd(); } - - /// - /// Converts a delegate to a function. - /// - /// The type of objects to compare. - /// The comparison delegate. - /// A function that compares two objects. - public static Func ToFunc(this Comparison comparer) + else { - return (t1, t2) => comparer(t1, t2); + InternalTryAdd(); } + } - /// - /// Determines whether two sequences are equal using a custom equality comparer function. - /// - /// The type of elements in the sequences. - /// The first sequence to compare. - /// The second sequence to compare. - /// The function to determine equality between elements. - /// True if the sequences are equal; otherwise, false. - public static bool SequenceEqual(this IEnumerable first, IEnumerable second, Func comparer) + /// + /// Adds a value to a collection if it does not already exist. + /// + /// The type of elements in the collection. + /// The collection to add the value to. + /// The value to add. + /// True if the value was added; false if it already existed. + /// Thrown when is null. + public static bool TryAdd(this ICollection collection, T value) + { + if (collection is null) + throw new ArgumentNullException(nameof(collection)); + + bool InternalTryAdd() { - return first.SequenceEqual(second, comparer.ToComparer()); + if (collection.Contains(value)) + return false; + + collection.Add(value); + return true; } - /// - /// Sorts a collection using a delegate. - /// - /// The type of elements in the collection. - /// The collection to sort. - /// The comparison delegate to determine order. - /// An ordered enumerable of the collection. - public static IOrderedEnumerable OrderBy(this IEnumerable collection, Comparison comparison) + if (collection is ISynchronizedCollection sync) { - return collection.OrderBy(item => item, comparison.ToComparer()); + lock (sync.SyncRoot) + return InternalTryAdd(); } - /// - /// Returns the index of the first element in a sequence that satisfies a specified condition. - /// - /// The type of elements in the sequence. - /// The sequence to search. - /// The condition to test each element against. - /// The zero-based index of the first matching element, or -1 if no element is found. - public static int IndexOf(this IEnumerable source, Func predicate) - { - var index = 0; + return InternalTryAdd(); + } - foreach (var t in source) - { - if (predicate(t)) - return index; + /// + /// Adds a key-value pair to a dictionary if the key does not already exist. + /// + /// The type of keys in the dictionary. + /// The type of values in the dictionary. + /// The dictionary to add the pair to. + /// The key to add. + /// The value to add. + /// True if the pair was added; false if the key already existed. + /// Thrown when is null. + public static bool TryAdd2(this IDictionary dict, TKey key, TValue value) + { + if (dict is null) + throw new ArgumentNullException(nameof(dict)); - index++; - } + bool InternalTryAdd() + { + if (dict.ContainsKey(key)) + return false; - return -1; + dict.Add(key, value); + return true; } - /// - /// Adds a range of values to a collection, ensuring no duplicates if the collection already contains them. - /// - /// The type of elements in the collection. - /// The collection to add values to. - /// The values to add. - /// Thrown when is null. - public static void TryAdd(this ICollection collection, IEnumerable values) + if (dict is ISynchronizedCollection sync) { - if (values is null) - throw new ArgumentNullException(nameof(values)); - - void InternalTryAdd() - { - foreach (var value in values) - { - if (!collection.Contains(value)) - collection.Add(value); - } - } - - if (collection is ISynchronizedCollection sync) - { - lock (sync.SyncRoot) - InternalTryAdd(); - } - else - { - InternalTryAdd(); - } + lock (sync.SyncRoot) + return InternalTryAdd(); } - /// - /// Adds a value to a collection if it does not already exist. - /// - /// The type of elements in the collection. - /// The collection to add the value to. - /// The value to add. - /// True if the value was added; false if it already existed. - /// Thrown when is null. - public static bool TryAdd(this ICollection collection, T value) - { - if (collection is null) - throw new ArgumentNullException(nameof(collection)); + return InternalTryAdd(); + } - bool InternalTryAdd() - { - if (collection.Contains(value)) - return false; + /// + /// Concatenates two collections of the same type into a new instance. + /// + /// The type of the collection, which must implement and have a parameterless constructor. + /// The type of elements in the collection. + /// The first collection. + /// The second collection. + /// A new collection containing all elements from both input collections. + public static T ConcatEx(this T first, T second) + where T : ICollection, new() + { + var retVal = new T(); + retVal.AddRange(first.Concat(second)); + return retVal; + } - collection.Add(value); - return true; - } + /// + /// Adds a range of items to a collection, using optimized methods when available. + /// + /// The type of elements in the collection. + /// The collection to add items to. + /// The items to add. + /// Thrown when or is null. + public static void AddRange(this ICollection source, IEnumerable items) + { + if (source is null) + throw new ArgumentNullException(nameof(source)); - if (collection is ISynchronizedCollection sync) - { - lock (sync.SyncRoot) - return InternalTryAdd(); - } + if (items is null) + throw new ArgumentNullException(nameof(items)); - return InternalTryAdd(); + if (source is List list) + list.AddRange(items); + else if (source is ICollectionEx ex) + ex.AddRange(items); + else if (source is ISet set) + set.UnionWith(items); + else + { + foreach (var item in items) + source.Add(item); } + } - /// - /// Adds a key-value pair to a dictionary if the key does not already exist. - /// - /// The type of keys in the dictionary. - /// The type of values in the dictionary. - /// The dictionary to add the pair to. - /// The key to add. - /// The value to add. - /// True if the pair was added; false if the key already existed. - /// Thrown when is null. - public static bool TryAdd2(this IDictionary dict, TKey key, TValue value) - { - if (dict is null) - throw new ArgumentNullException(nameof(dict)); + /// + /// Removes a range of items from a collection, using optimized methods when available. + /// + /// The type of elements in the collection. + /// The collection to remove items from. + /// The items to remove. + /// Thrown when is null. + public static void RemoveRange(this ICollection source, IEnumerable items) + { + if (items is null) + throw new ArgumentNullException(nameof(items)); - bool InternalTryAdd() - { - if (dict.ContainsKey(key)) - return false; + if (source is ICollectionEx ex) + ex.RemoveRange(items); + else + items.ForEach(i => source.Remove(i)); + } - dict.Add(key, value); - return true; - } + /// + /// Removes items from a list that match a specified filter and adjusts the list size. + /// + /// The type of elements in the list. + /// The list to remove items from. + /// The condition to identify items to remove. + /// The number of items removed. + public static int RemoveWhere2(this IList list, Func filter) + { + // https://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,82567b42bbfc416e,references - if (dict is ISynchronizedCollection sync) - { - lock (sync.SyncRoot) - return InternalTryAdd(); - } + var newLen = 0; + var len = list.Count; - return InternalTryAdd(); - } + // Find the first item which needs to be removed. + while(newLen < len && !filter(list[newLen])) + newLen++; - /// - /// Concatenates two collections of the same type into a new instance. - /// - /// The type of the collection, which must implement and have a parameterless constructor. - /// The type of elements in the collection. - /// The first collection. - /// The second collection. - /// A new collection containing all elements from both input collections. - public static T ConcatEx(this T first, T second) - where T : ICollection, new() - { - var retVal = new T(); - retVal.AddRange(first.Concat(second)); - return retVal; - } + if(newLen >= len) + return 0; - /// - /// Adds a range of items to a collection, using optimized methods when available. - /// - /// The type of elements in the collection. - /// The collection to add items to. - /// The items to add. - /// Thrown when or is null. - public static void AddRange(this ICollection source, IEnumerable items) - { - if (source is null) - throw new ArgumentNullException(nameof(source)); - - if (items is null) - throw new ArgumentNullException(nameof(items)); - - if (source is List list) - list.AddRange(items); - else if (source is ICollectionEx ex) - ex.AddRange(items); - else if (source is ISet set) - set.UnionWith(items); - else - { - foreach (var item in items) - source.Add(item); - } - } + var current = newLen + 1; - /// - /// Removes a range of items from a collection, using optimized methods when available. - /// - /// The type of elements in the collection. - /// The collection to remove items from. - /// The items to remove. - /// Thrown when is null. - public static void RemoveRange(this ICollection source, IEnumerable items) + while(current < len) { - if (items is null) - throw new ArgumentNullException(nameof(items)); + // Find the first item which needs to be kept. + while(current < len && filter(list[current])) + current++; - if (source is ICollectionEx ex) - ex.RemoveRange(items); - else - items.ForEach(i => source.Remove(i)); + if(current < len) { + // copy item to the free slot. + list[newLen++] = list[current++]; + } } - /// - /// Removes items from a list that match a specified filter and adjusts the list size. - /// - /// The type of elements in the list. - /// The list to remove items from. - /// The condition to identify items to remove. - /// The number of items removed. - public static int RemoveWhere2(this IList list, Func filter) - { - // https://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,82567b42bbfc416e,references + while(list.Count > newLen) + list.RemoveAt(list.Count - 1); - var newLen = 0; - var len = list.Count; + return len - newLen; + } - // Find the first item which needs to be removed. - while(newLen < len && !filter(list[newLen])) - newLen++; + /// + /// Removes items from a collection that match a specified filter and returns the removed items. + /// + /// The type of elements in the collection. + /// The collection to remove items from. + /// The condition to identify items to remove. + /// An enumerable of the removed items. + public static IEnumerable RemoveWhere(this ICollection collection, Func filter) + { + var removingItems = collection.Where(filter).ToArray(); - if(newLen >= len) - return 0; + foreach (var t in removingItems) + collection.Remove(t); - var current = newLen + 1; + return removingItems; + } - while(current < len) - { - // Find the first item which needs to be kept. - while(current < len && filter(list[current])) - current++; + /// + /// Flattens a sequence of sequences into a single sequence. + /// + /// The type of elements in the sequences. + /// The sequence of sequences to flatten. + /// A single sequence containing all elements from the input sequences. + public static IEnumerable SelectMany(this IEnumerable> values) + { + return values.SelectMany(value => value); + } - if(current < len) { - // copy item to the free slot. - list[newLen++] = list[current++]; - } - } + /// + /// Orders a sequence of values in ascending order using the default comparer. + /// + /// The type of elements in the sequence. + /// The sequence to order. + /// An ordered sequence of the input values. + public static IEnumerable OrderBy(this IEnumerable values) + { + return values.OrderBy(value => value); + } - while(list.Count > newLen) - list.RemoveAt(list.Count - 1); + /// + /// Orders a sequence of values in descending order using the default comparer. + /// + /// The type of elements in the sequence. + /// The sequence to order. + /// An ordered sequence of the input values in descending order. + public static IEnumerable OrderByDescending(this IEnumerable values) + { + return values.OrderByDescending(value => value); + } - return len - newLen; - } + /// + /// Computes a hash code for a sequence of elements. + /// + /// The type of elements in the sequence. + /// The sequence to compute the hash code for. + /// A hash code for the sequence. + /// Thrown when is null. + public static int GetHashCodeEx(this IEnumerable collection) + { + if (collection is null) + throw new ArgumentNullException(nameof(collection)); - /// - /// Removes items from a collection that match a specified filter and returns the removed items. - /// - /// The type of elements in the collection. - /// The collection to remove items from. - /// The condition to identify items to remove. - /// An enumerable of the removed items. - public static IEnumerable RemoveWhere(this ICollection collection, Func filter) + unchecked { - var removingItems = collection.Where(filter).ToArray(); + var hash = 0; - foreach (var t in removingItems) - collection.Remove(t); + var index = 0; + foreach (var item in collection) + hash ^= (31 ^ index++) * (item?.GetHashCode() ?? 0); - return removingItems; + hash %= 2 ^ 32; + return hash; } + } - /// - /// Flattens a sequence of sequences into a single sequence. - /// - /// The type of elements in the sequences. - /// The sequence of sequences to flatten. - /// A single sequence containing all elements from the input sequences. - public static IEnumerable SelectMany(this IEnumerable> values) - { - return values.SelectMany(value => value); - } + /// + /// Determines whether a sequence contains any null items. + /// + /// The type of elements in the sequence, which must be a reference type. + /// The sequence to check. + /// True if the sequence contains null; otherwise, false. + public static bool HasNullItem(this IEnumerable items) + where T : class + { + return items.Contains(null); + } - /// - /// Orders a sequence of values in ascending order using the default comparer. - /// - /// The type of elements in the sequence. - /// The sequence to order. - /// An ordered sequence of the input values. - public static IEnumerable OrderBy(this IEnumerable values) + /// + /// Copies all items from a collection to an array and clears the collection. + /// + /// The type of elements in the collection. + /// The collection to copy and clear. + /// An array containing the copied items. + public static T[] CopyAndClear(this ICollection items) + { + T[] InternalCopyAndClear() { - return values.OrderBy(value => value); + var retVal = items.ToArray(); + items.Clear(); + return retVal; } - /// - /// Orders a sequence of values in descending order using the default comparer. - /// - /// The type of elements in the sequence. - /// The sequence to order. - /// An ordered sequence of the input values in descending order. - public static IEnumerable OrderByDescending(this IEnumerable values) + if (items is not ISynchronizedCollection sync) return InternalCopyAndClear(); + + lock (sync.SyncRoot) + return InternalCopyAndClear(); + } + + /// + /// Retrieves a value from a dictionary by key and removes the key-value pair. + /// + /// The type of keys in the dictionary. + /// The type of values in the dictionary. + /// The dictionary to operate on. + /// The key of the value to retrieve and remove. + /// The value associated with the key. + public static TValue GetAndRemove(this IDictionary dict, TKey key) + { + TValue InternalGetAndRemove() { - return values.OrderByDescending(value => value); + var value = dict[key]; + dict.Remove(key); + return value; } - /// - /// Computes a hash code for a sequence of elements. - /// - /// The type of elements in the sequence. - /// The sequence to compute the hash code for. - /// A hash code for the sequence. - /// Thrown when is null. - public static int GetHashCodeEx(this IEnumerable collection) - { - if (collection is null) - throw new ArgumentNullException(nameof(collection)); + if (dict is not ISynchronizedCollection sync) return InternalGetAndRemove(); - unchecked - { - var hash = 0; + lock (sync.SyncRoot) + return InternalGetAndRemove(); + } - var index = 0; - foreach (var item in collection) - hash ^= (31 ^ index++) * (item?.GetHashCode() ?? 0); + /// + /// Attempts to retrieve a value from a dictionary by key and remove it, returning null if not found (for value types). + /// + /// The type of keys in the dictionary. + /// The type of values in the dictionary, which must be a value type. + /// The dictionary to operate on. + /// The key of the value to retrieve and remove. + /// The value if found and removed; otherwise, null. + public static TValue? TryGetAndRemove2(this IDictionary dict, TKey key) + where TValue : struct + { + return dict.TryGetAndRemove(key, out var value) ? value : (TValue?)null; + } - hash %= 2 ^ 32; - return hash; - } - } + /// + /// Attempts to retrieve a value from a dictionary by key and remove it, returning the default value if not found. + /// + /// The type of keys in the dictionary. + /// The type of values in the dictionary. + /// The dictionary to operate on. + /// The key of the value to retrieve and remove. + /// The value if found and removed; otherwise, the default value of . + public static TValue TryGetAndRemove(this IDictionary dict, TKey key) + { + return dict.TryGetAndRemove(key, out var value) ? value : default; + } - /// - /// Determines whether a sequence contains any null items. - /// - /// The type of elements in the sequence, which must be a reference type. - /// The sequence to check. - /// True if the sequence contains null; otherwise, false. - public static bool HasNullItem(this IEnumerable items) - where T : class + /// + /// Attempts to retrieve a value from a dictionary by key and remove it, indicating success. + /// + /// The type of keys in the dictionary. + /// The type of values in the dictionary. + /// The dictionary to operate on. + /// The key of the value to retrieve and remove. + /// When this method returns, contains the value if found; otherwise, the default value. + /// True if the value was found and removed; otherwise, false. + public static bool TryGetAndRemove(this IDictionary dict, TKey key, out TValue value) + { + bool InternalTryGetAndRemove(out TValue value2) { - return items.Contains(null); + if (!dict.TryGetValue(key, out value2)) + return false; + + dict.Remove(key); + return true; } - /// - /// Copies all items from a collection to an array and clears the collection. - /// - /// The type of elements in the collection. - /// The collection to copy and clear. - /// An array containing the copied items. - public static T[] CopyAndClear(this ICollection items) + if (dict is ISynchronizedCollection sync) { - T[] InternalCopyAndClear() - { - var retVal = items.ToArray(); - items.Clear(); - return retVal; - } - - if (items is not ISynchronizedCollection sync) return InternalCopyAndClear(); - lock (sync.SyncRoot) - return InternalCopyAndClear(); + return InternalTryGetAndRemove(out value); } - /// - /// Retrieves a value from a dictionary by key and removes the key-value pair. - /// - /// The type of keys in the dictionary. - /// The type of values in the dictionary. - /// The dictionary to operate on. - /// The key of the value to retrieve and remove. - /// The value associated with the key. - public static TValue GetAndRemove(this IDictionary dict, TKey key) - { - TValue InternalGetAndRemove() - { - var value = dict[key]; - dict.Remove(key); - return value; - } + return InternalTryGetAndRemove(out value); + } - if (dict is not ISynchronizedCollection sync) return InternalGetAndRemove(); + /// + /// Retrieves an element from a sequence counting from the end. + /// + /// The type of elements in the sequence. + /// The sequence to query. + /// The zero-based index from the end (0 is the last element). + /// The element at the specified position from the end. + public static T ElementAtFromEnd(this IEnumerable source, int index) + { + return source.ElementAt(source.GetIndexFromEnd(index)); + } - lock (sync.SyncRoot) - return InternalGetAndRemove(); - } + /// + /// Retrieves an element from a sequence counting from the end, or the default value if out of range. + /// + /// The type of elements in the sequence. + /// The sequence to query. + /// The zero-based index from the end (0 is the last element). + /// The element at the specified position from the end, or the default value if out of range. + public static T ElementAtFromEndOrDefault(this IEnumerable source, int index) + { + return source.ElementAtOrDefault(source.GetIndexFromEnd(index)); + } - /// - /// Attempts to retrieve a value from a dictionary by key and remove it, returning null if not found (for value types). - /// - /// The type of keys in the dictionary. - /// The type of values in the dictionary, which must be a value type. - /// The dictionary to operate on. - /// The key of the value to retrieve and remove. - /// The value if found and removed; otherwise, null. - public static TValue? TryGetAndRemove2(this IDictionary dict, TKey key) - where TValue : struct - { - return dict.TryGetAndRemove(key, out var value) ? value : (TValue?)null; - } + /// + /// Calculates the index from the start of a sequence given an index from the end. + /// + /// The type of elements in the sequence. + /// The sequence to query. + /// The zero-based index from the end. + /// The zero-based index from the start. + private static int GetIndexFromEnd(this IEnumerable source, int index) + { + return source.Count() - 1 - index; + } - /// - /// Attempts to retrieve a value from a dictionary by key and remove it, returning the default value if not found. - /// - /// The type of keys in the dictionary. - /// The type of values in the dictionary. - /// The dictionary to operate on. - /// The key of the value to retrieve and remove. - /// The value if found and removed; otherwise, the default value of . - public static TValue TryGetAndRemove(this IDictionary dict, TKey key) - { - return dict.TryGetAndRemove(key, out var value) ? value : default; - } - - /// - /// Attempts to retrieve a value from a dictionary by key and remove it, indicating success. - /// - /// The type of keys in the dictionary. - /// The type of values in the dictionary. - /// The dictionary to operate on. - /// The key of the value to retrieve and remove. - /// When this method returns, contains the value if found; otherwise, the default value. - /// True if the value was found and removed; otherwise, false. - public static bool TryGetAndRemove(this IDictionary dict, TKey key, out TValue value) - { - bool InternalTryGetAndRemove(out TValue value2) - { - if (!dict.TryGetValue(key, out value2)) - return false; - - dict.Remove(key); - return true; - } - - if (dict is ISynchronizedCollection sync) - { - lock (sync.SyncRoot) - return InternalTryGetAndRemove(out value); - } - - return InternalTryGetAndRemove(out value); - } - - /// - /// Retrieves an element from a sequence counting from the end. - /// - /// The type of elements in the sequence. - /// The sequence to query. - /// The zero-based index from the end (0 is the last element). - /// The element at the specified position from the end. - public static T ElementAtFromEnd(this IEnumerable source, int index) - { - return source.ElementAt(source.GetIndexFromEnd(index)); - } + /// + /// Retrieves an element from a linked list counting from the end. + /// + /// The type of elements in the linked list. + /// The linked list to query. + /// The zero-based index from the end (0 is the last element). + /// The element at the specified position from the end. + /// Thrown when is null. + /// Thrown when the list is empty or the index is out of range. + public static T ElementAtFromEnd(this LinkedList list, int index) + { + if (list is null) + throw new ArgumentNullException(nameof(list)); - /// - /// Retrieves an element from a sequence counting from the end, or the default value if out of range. - /// - /// The type of elements in the sequence. - /// The sequence to query. - /// The zero-based index from the end (0 is the last element). - /// The element at the specified position from the end, or the default value if out of range. - public static T ElementAtFromEndOrDefault(this IEnumerable source, int index) - { - return source.ElementAtOrDefault(source.GetIndexFromEnd(index)); - } + if (list.IsEmpty()) + throw new ArgumentOutOfRangeException(nameof(list)); - /// - /// Calculates the index from the start of a sequence given an index from the end. - /// - /// The type of elements in the sequence. - /// The sequence to query. - /// The zero-based index from the end. - /// The zero-based index from the start. - private static int GetIndexFromEnd(this IEnumerable source, int index) - { - return source.Count() - 1 - index; - } + var curr = list.Last; - /// - /// Retrieves an element from a linked list counting from the end. - /// - /// The type of elements in the linked list. - /// The linked list to query. - /// The zero-based index from the end (0 is the last element). - /// The element at the specified position from the end. - /// Thrown when is null. - /// Thrown when the list is empty or the index is out of range. - public static T ElementAtFromEnd(this LinkedList list, int index) + while (index > 0) { - if (list is null) - throw new ArgumentNullException(nameof(list)); + curr = curr.Previous; + index--; - if (list.IsEmpty()) + if (curr is null) throw new ArgumentOutOfRangeException(nameof(list)); + } - var curr = list.Last; + return curr.Value; + } - while (index > 0) - { - curr = curr.Previous; - index--; + /// + /// Retrieves an element from a synchronized linked list counting from the end. + /// + /// The type of elements in the linked list. + /// The synchronized linked list to query. + /// The zero-based index from the end (0 is the last element). + /// The element at the specified position from the end. + /// Thrown when is null. + /// Thrown when the list is empty or the index is out of range. + public static T ElementAtFromEnd(this SynchronizedLinkedList list, int index) + { + if (list is null) + throw new ArgumentNullException(nameof(list)); - if (curr is null) - throw new ArgumentOutOfRangeException(nameof(list)); - } + if (list.IsEmpty()) + throw new ArgumentOutOfRangeException(nameof(list)); - return curr.Value; - } + var curr = list.Last; - /// - /// Retrieves an element from a synchronized linked list counting from the end. - /// - /// The type of elements in the linked list. - /// The synchronized linked list to query. - /// The zero-based index from the end (0 is the last element). - /// The element at the specified position from the end. - /// Thrown when is null. - /// Thrown when the list is empty or the index is out of range. - public static T ElementAtFromEnd(this SynchronizedLinkedList list, int index) + while (index > 0) { - if (list is null) - throw new ArgumentNullException(nameof(list)); + curr = curr.Previous; + index--; - if (list.IsEmpty()) + if (curr is null) throw new ArgumentOutOfRangeException(nameof(list)); + } - var curr = list.Last; + return curr.Value; + } - while (index > 0) - { - curr = curr.Previous; - index--; + /// + /// Retrieves an element from a linked list counting from the end, or the default value if out of range. + /// + /// The type of elements in the linked list. + /// The linked list to query. + /// The zero-based index from the end (0 is the last element). + /// The element at the specified position from the end, or the default value if out of range. + /// Thrown when is null. + public static T ElementAtFromEndOrDefault(this LinkedList list, int index) + { + if (list is null) + throw new ArgumentNullException(nameof(list)); - if (curr is null) - throw new ArgumentOutOfRangeException(nameof(list)); - } + var curr = list.Last; - return curr.Value; + while (index > 0 && curr != null) + { + curr = curr.Previous; + index--; } - /// - /// Retrieves an element from a linked list counting from the end, or the default value if out of range. - /// - /// The type of elements in the linked list. - /// The linked list to query. - /// The zero-based index from the end (0 is the last element). - /// The element at the specified position from the end, or the default value if out of range. - /// Thrown when is null. - public static T ElementAtFromEndOrDefault(this LinkedList list, int index) - { - if (list is null) - throw new ArgumentNullException(nameof(list)); + return curr is null ? default : curr.Value; + } - var curr = list.Last; + /// + /// Retrieves an element from a synchronized linked list counting from the end, or the default value if out of range. + /// + /// The type of elements in the linked list. + /// The synchronized linked list to query. + /// The zero-based index from the end (0 is the last element). + /// The element at the specified position from the end, or the default value if out of range. + /// Thrown when is null. + public static T ElementAtFromEndOrDefault(this SynchronizedLinkedList list, int index) + { + if (list is null) + throw new ArgumentNullException(nameof(list)); - while (index > 0 && curr != null) - { - curr = curr.Previous; - index--; - } + var curr = list.Last; - return curr is null ? default : curr.Value; + while (index > 0 && curr != null) + { + curr = curr.Previous; + index--; } - /// - /// Retrieves an element from a synchronized linked list counting from the end, or the default value if out of range. - /// - /// The type of elements in the linked list. - /// The synchronized linked list to query. - /// The zero-based index from the end (0 is the last element). - /// The element at the specified position from the end, or the default value if out of range. - /// Thrown when is null. - public static T ElementAtFromEndOrDefault(this SynchronizedLinkedList list, int index) - { - if (list is null) - throw new ArgumentNullException(nameof(list)); + return curr is null ? default : curr.Value; + } - var curr = list.Last; + /// + /// Converts a sequence of key-value pairs into a with the default equality comparer. + /// + /// The type of keys. + /// The type of values. + /// The sequence of key-value pairs. + /// A containing the key-value pairs. + public static PairSet ToPairSet(this IEnumerable> source) + { + return source.ToPairSet(System.Collections.Generic.EqualityComparer.Default); + } - while (index > 0 && curr != null) - { - curr = curr.Previous; - index--; - } + /// + /// Converts a sequence of key-value pairs into a with a specified equality comparer. + /// + /// The type of keys. + /// The type of values. + /// The sequence of key-value pairs. + /// The equality comparer for keys. + /// A containing the key-value pairs. + /// Thrown when is null. + public static PairSet ToPairSet(this IEnumerable> source, IEqualityComparer comparer) + { + if (source is null) + throw new ArgumentNullException(nameof(source)); - return curr is null ? default : curr.Value; - } + var set = new PairSet(comparer); - /// - /// Converts a sequence of key-value pairs into a with the default equality comparer. - /// - /// The type of keys. - /// The type of values. - /// The sequence of key-value pairs. - /// A containing the key-value pairs. - public static PairSet ToPairSet(this IEnumerable> source) + foreach (var item in source) { - return source.ToPairSet(System.Collections.Generic.EqualityComparer.Default); + set.Add(item.Key, item.Value); } - /// - /// Converts a sequence of key-value pairs into a with a specified equality comparer. - /// - /// The type of keys. - /// The type of values. - /// The sequence of key-value pairs. - /// The equality comparer for keys. - /// A containing the key-value pairs. - /// Thrown when is null. - public static PairSet ToPairSet(this IEnumerable> source, IEqualityComparer comparer) - { - if (source is null) - throw new ArgumentNullException(nameof(source)); + return set; + } - var set = new PairSet(comparer); + /// + /// Converts a sequence into a using selector functions for keys and values. + /// + /// The type of elements in the source sequence. + /// The type of keys. + /// The type of values. + /// The source sequence. + /// The function to extract keys from elements. + /// The function to extract values from elements. + /// A containing the transformed key-value pairs. + /// Thrown when , , or is null. + public static PairSet ToPairSet(this IEnumerable source, Func keySelector, Func valueSelector) + { + if (source is null) + throw new ArgumentNullException(nameof(source)); - foreach (var item in source) - { - set.Add(item.Key, item.Value); - } + if (keySelector is null) + throw new ArgumentNullException(nameof(keySelector)); - return set; - } + if (valueSelector is null) + throw new ArgumentNullException(nameof(valueSelector)); - /// - /// Converts a sequence into a using selector functions for keys and values. - /// - /// The type of elements in the source sequence. - /// The type of keys. - /// The type of values. - /// The source sequence. - /// The function to extract keys from elements. - /// The function to extract values from elements. - /// A containing the transformed key-value pairs. - /// Thrown when , , or is null. - public static PairSet ToPairSet(this IEnumerable source, Func keySelector, Func valueSelector) - { - if (source is null) - throw new ArgumentNullException(nameof(source)); + var set = new PairSet(); - if (keySelector is null) - throw new ArgumentNullException(nameof(keySelector)); + var index = 0; - if (valueSelector is null) - throw new ArgumentNullException(nameof(valueSelector)); + foreach (var item in source) + { + set.Add(keySelector(item, index), valueSelector(item, index)); + index++; + } - var set = new PairSet(); + return set; + } - var index = 0; + #region Dictionary Methods - foreach (var item in source) - { - set.Add(keySelector(item, index), valueSelector(item, index)); - index++; - } + /// + /// Copies key-value pairs from a sequence to a dictionary. + /// + /// The type of keys. + /// The type of values. + /// The sequence of key-value pairs. + /// The dictionary to copy to. + /// Thrown when or is null. + public static void CopyTo(this IEnumerable> source, IDictionary destination) + { + if (source is null) + throw new ArgumentNullException(nameof(source)); - return set; - } + if (destination is null) + throw new ArgumentNullException(nameof(destination)); - #region Dictionary Methods + foreach (var pair in source) + destination.Add(pair); + } - /// - /// Copies key-value pairs from a sequence to a dictionary. - /// - /// The type of keys. - /// The type of values. - /// The sequence of key-value pairs. - /// The dictionary to copy to. - /// Thrown when or is null. - public static void CopyTo(this IEnumerable> source, IDictionary destination) - { - if (source is null) - throw new ArgumentNullException(nameof(source)); + /// + /// Converts a non-generic dictionary to a strongly-typed dictionary. + /// + /// The type of keys. + /// The type of values. + /// The non-generic dictionary. + /// A strongly-typed dictionary with the same key-value pairs. + /// Thrown when is null. + public static IDictionary TypedAs(this IDictionary dictionary) + { + if (dictionary is null) + throw new ArgumentNullException(nameof(dictionary)); - if (destination is null) - throw new ArgumentNullException(nameof(destination)); + return dictionary.Cast().ToDictionary(item => item.Key.To(), item => item.Value.To()); + } - foreach (var pair in source) - destination.Add(pair); - } + /// + /// Converts a sequence of key-value pairs to a dictionary using the default equality comparer. + /// + /// The type of keys. + /// The type of values. + /// The sequence of key-value pairs. + /// A dictionary containing the key-value pairs. + public static IDictionary ToDictionary(this IEnumerable> source) + { + return source.ToDictionary(pair => pair.Key, pair => pair.Value); + } - /// - /// Converts a non-generic dictionary to a strongly-typed dictionary. - /// - /// The type of keys. - /// The type of values. - /// The non-generic dictionary. - /// A strongly-typed dictionary with the same key-value pairs. - /// Thrown when is null. - public static IDictionary TypedAs(this IDictionary dictionary) - { - if (dictionary is null) - throw new ArgumentNullException(nameof(dictionary)); + /// + /// Converts a sequence of key-value pairs to a dictionary with a specified equality comparer. + /// + /// The type of keys. + /// The type of values. + /// The sequence of key-value pairs. + /// The equality comparer for keys. + /// A dictionary containing the key-value pairs. + public static IDictionary ToDictionary(this IEnumerable> source, IEqualityComparer comparer) + { + return source.ToDictionary(pair => pair.Key, pair => pair.Value, comparer); + } - return dictionary.Cast().ToDictionary(item => item.Key.To(), item => item.Value.To()); - } + /// + /// Converts a sequence of tuples to a dictionary using the default equality comparer. + /// + /// The type of keys. + /// The type of values. + /// The sequence of tuples. + /// A dictionary containing the key-value pairs from the tuples. + public static IDictionary ToDictionary(this IEnumerable> source) + { + return source.ToDictionary(pair => pair.Item1, pair => pair.Item2); + } - /// - /// Converts a sequence of key-value pairs to a dictionary using the default equality comparer. - /// - /// The type of keys. - /// The type of values. - /// The sequence of key-value pairs. - /// A dictionary containing the key-value pairs. - public static IDictionary ToDictionary(this IEnumerable> source) - { - return source.ToDictionary(pair => pair.Key, pair => pair.Value); - } + /// + /// Converts a sequence of tuples to a dictionary with a specified equality comparer. + /// + /// The type of keys. + /// The type of values. + /// The sequence of tuples. + /// The equality comparer for keys. + /// A dictionary containing the key-value pairs from the tuples. + public static IDictionary ToDictionary(this IEnumerable> source, IEqualityComparer comparer) + { + return source.ToDictionary(pair => pair.Item1, pair => pair.Item2, comparer); + } - /// - /// Converts a sequence of key-value pairs to a dictionary with a specified equality comparer. - /// - /// The type of keys. - /// The type of values. - /// The sequence of key-value pairs. - /// The equality comparer for keys. - /// A dictionary containing the key-value pairs. - public static IDictionary ToDictionary(this IEnumerable> source, IEqualityComparer comparer) - { - return source.ToDictionary(pair => pair.Key, pair => pair.Value, comparer); - } + /// + /// Converts a key-value pair to a tuple. + /// + /// The type of the key. + /// The type of the value. + /// The key-value pair to convert. + /// A tuple containing the key and value. + public static Tuple ToTuple(this KeyValuePair pair) + { + return Tuple.Create(pair.Key, pair.Value); + } - /// - /// Converts a sequence of tuples to a dictionary using the default equality comparer. - /// - /// The type of keys. - /// The type of values. - /// The sequence of tuples. - /// A dictionary containing the key-value pairs from the tuples. - public static IDictionary ToDictionary(this IEnumerable> source) - { - return source.ToDictionary(pair => pair.Item1, pair => pair.Item2); - } + /// + /// Converts a tuple to a key-value pair. + /// + /// The type of the key. + /// The type of the value. + /// The tuple to convert. + /// A key-value pair containing the tuple's items. + /// Thrown when is null. + public static KeyValuePair ToPair(this Tuple pair) + { + if (pair is null) + throw new ArgumentNullException(nameof(pair)); - /// - /// Converts a sequence of tuples to a dictionary with a specified equality comparer. - /// - /// The type of keys. - /// The type of values. - /// The sequence of tuples. - /// The equality comparer for keys. - /// A dictionary containing the key-value pairs from the tuples. - public static IDictionary ToDictionary(this IEnumerable> source, IEqualityComparer comparer) - { - return source.ToDictionary(pair => pair.Item1, pair => pair.Item2, comparer); - } + return new KeyValuePair(pair.Item1, pair.Item2); + } - /// - /// Converts a key-value pair to a tuple. - /// - /// The type of the key. - /// The type of the value. - /// The key-value pair to convert. - /// A tuple containing the key and value. - public static Tuple ToTuple(this KeyValuePair pair) - { - return Tuple.Create(pair.Key, pair.Value); - } + /// + /// Converts a sequence into a dictionary using selector functions for keys and values. + /// + /// The type of elements in the source sequence. + /// The type of keys. + /// The type of values. + /// The source sequence. + /// The function to extract keys from elements. + /// The function to extract values from elements. + /// A dictionary containing the transformed key-value pairs. + /// Thrown when , , or is null. + public static IDictionary ToDictionary(this IEnumerable source, Func keySelector, Func valueSelector) + { + if (source is null) + throw new ArgumentNullException(nameof(source)); - /// - /// Converts a tuple to a key-value pair. - /// - /// The type of the key. - /// The type of the value. - /// The tuple to convert. - /// A key-value pair containing the tuple's items. - /// Thrown when is null. - public static KeyValuePair ToPair(this Tuple pair) - { - if (pair is null) - throw new ArgumentNullException(nameof(pair)); + if (keySelector is null) + throw new ArgumentNullException(nameof(keySelector)); - return new KeyValuePair(pair.Item1, pair.Item2); - } + if (valueSelector is null) + throw new ArgumentNullException(nameof(valueSelector)); - /// - /// Converts a sequence into a dictionary using selector functions for keys and values. - /// - /// The type of elements in the source sequence. - /// The type of keys. - /// The type of values. - /// The source sequence. - /// The function to extract keys from elements. - /// The function to extract values from elements. - /// A dictionary containing the transformed key-value pairs. - /// Thrown when , , or is null. - public static IDictionary ToDictionary(this IEnumerable source, Func keySelector, Func valueSelector) - { - if (source is null) - throw new ArgumentNullException(nameof(source)); + var dict = new Dictionary(); - if (keySelector is null) - throw new ArgumentNullException(nameof(keySelector)); + var index = 0; - if (valueSelector is null) - throw new ArgumentNullException(nameof(valueSelector)); + foreach (var item in source) + { + dict.Add(keySelector(item, index), valueSelector(item, index)); + index++; + } - var dict = new Dictionary(); + return dict; + } - var index = 0; + /// + /// Converts a grouping into a dictionary where each key maps to its group of values. + /// + /// The type of keys. + /// The type of values. + /// The grouping to convert. + /// A dictionary mapping keys to their groups. + /// Thrown when is null. + public static IDictionary> ToDictionary(this IEnumerable> grouping) + { + if (grouping is null) + throw new ArgumentNullException(nameof(grouping)); - foreach (var item in source) - { - dict.Add(keySelector(item, index), valueSelector(item, index)); - index++; - } + return grouping.ToDictionary(g => g.Key, g => (IEnumerable)g); + } - return dict; - } + /// + /// Retrieves all keys in a dictionary that map to a specific value. + /// + /// The type of keys. + /// The type of values. + /// The dictionary to query. + /// The value to match. + /// An enumerable of keys associated with the specified value. + public static IEnumerable GetKeys(this IDictionary dictionary, TValue value) + { + return from pair in dictionary where pair.Value.Equals(value) select pair.Key; + } - /// - /// Converts a grouping into a dictionary where each key maps to its group of values. - /// - /// The type of keys. - /// The type of values. - /// The grouping to convert. - /// A dictionary mapping keys to their groups. - /// Thrown when is null. - public static IDictionary> ToDictionary(this IEnumerable> grouping) - { - if (grouping is null) - throw new ArgumentNullException(nameof(grouping)); + /// + /// Adds a new key to a dictionary with a default value if it doesn't exist, and returns the value. + /// + /// The type of keys. + /// The type of values. + /// The dictionary to operate on. + /// The key to add or retrieve. + /// The existing or newly created value. + public static TValue SafeAdd(this IDictionary dictionary, TKey key) + { + return dictionary.SafeAdd(key, out _); + } - return grouping.ToDictionary(g => g.Key, g => (IEnumerable)g); - } + /// + /// A private cache class to provide fast activation of default instances for . + /// + private static class FastActivatorCache + { + public static readonly Func Activator; - /// - /// Retrieves all keys in a dictionary that map to a specific value. - /// - /// The type of keys. - /// The type of values. - /// The dictionary to query. - /// The value to match. - /// An enumerable of keys associated with the specified value. - public static IEnumerable GetKeys(this IDictionary dictionary, TValue value) + static FastActivatorCache() { - return from pair in dictionary where pair.Value.Equals(value) select pair.Key; + Activator = k => FastActivator.CreateObject(); } + } - /// - /// Adds a new key to a dictionary with a default value if it doesn't exist, and returns the value. - /// - /// The type of keys. - /// The type of values. - /// The dictionary to operate on. - /// The key to add or retrieve. - /// The existing or newly created value. - public static TValue SafeAdd(this IDictionary dictionary, TKey key) - { - return dictionary.SafeAdd(key, out _); - } + /// + /// Adds a new key to a dictionary with a default value if it doesn't exist, indicating whether it was new. + /// + /// The type of keys. + /// The type of values. + /// The dictionary to operate on. + /// The key to add or retrieve. + /// When this method returns, indicates whether the key was newly added. + /// The existing or newly created value. + public static TValue SafeAdd(this IDictionary dictionary, TKey key, out bool isNew) + { + return dictionary.SafeAdd(key, FastActivatorCache.Activator, out isNew); + } - /// - /// A private cache class to provide fast activation of default instances for . - /// - private static class FastActivatorCache - { - public static readonly Func Activator; + /// + /// Adds a new key to a dictionary with a value generated by a handler if it doesn't exist. + /// + /// The type of keys. + /// The type of values. + /// The dictionary to operate on. + /// The key to add or retrieve. + /// The function to generate a value if the key is new. + /// The existing or newly created value. + /// Thrown when or is null. + public static TValue SafeAdd(this IDictionary dictionary, TKey key, Func handler) + { + return dictionary.SafeAdd(key, handler, out _); + } - static FastActivatorCache() - { - Activator = k => FastActivator.CreateObject(); - } - } + /// + /// Adds a new key to a dictionary with a value generated by a handler if it doesn't exist, indicating whether it was new. + /// + /// The type of keys. + /// The type of values. + /// The dictionary to operate on. + /// The key to add or retrieve. + /// The function to generate a value if the key is new. + /// When this method returns, indicates whether the key was newly added. + /// The existing or newly created value. + /// Thrown when or is null. + public static TValue SafeAdd(this IDictionary dictionary, TKey key, Func handler, out bool isNew) + { + if (dictionary is null) + throw new ArgumentNullException(nameof(dictionary)); - /// - /// Adds a new key to a dictionary with a default value if it doesn't exist, indicating whether it was new. - /// - /// The type of keys. - /// The type of values. - /// The dictionary to operate on. - /// The key to add or retrieve. - /// When this method returns, indicates whether the key was newly added. - /// The existing or newly created value. - public static TValue SafeAdd(this IDictionary dictionary, TKey key, out bool isNew) - { - return dictionary.SafeAdd(key, FastActivatorCache.Activator, out isNew); - } + if (handler is null) + throw new ArgumentNullException(nameof(handler)); - /// - /// Adds a new key to a dictionary with a value generated by a handler if it doesn't exist. - /// - /// The type of keys. - /// The type of values. - /// The dictionary to operate on. - /// The key to add or retrieve. - /// The function to generate a value if the key is new. - /// The existing or newly created value. - /// Thrown when or is null. - public static TValue SafeAdd(this IDictionary dictionary, TKey key, Func handler) - { - return dictionary.SafeAdd(key, handler, out _); - } + isNew = false; - /// - /// Adds a new key to a dictionary with a value generated by a handler if it doesn't exist, indicating whether it was new. - /// - /// The type of keys. - /// The type of values. - /// The dictionary to operate on. - /// The key to add or retrieve. - /// The function to generate a value if the key is new. - /// When this method returns, indicates whether the key was newly added. - /// The existing or newly created value. - /// Thrown when or is null. - public static TValue SafeAdd(this IDictionary dictionary, TKey key, Func handler, out bool isNew) + if (!dictionary.TryGetValue(key, out var value)) { - if (dictionary is null) - throw new ArgumentNullException(nameof(dictionary)); - - if (handler is null) - throw new ArgumentNullException(nameof(handler)); - - isNew = false; + var syncObj = (dictionary as ISynchronizedCollection)?.SyncRoot ?? (object)dictionary; - if (!dictionary.TryGetValue(key, out var value)) + lock (syncObj) { - var syncObj = (dictionary as ISynchronizedCollection)?.SyncRoot ?? (object)dictionary; - - lock (syncObj) + if (!dictionary.TryGetValue(key, out value)) { - if (!dictionary.TryGetValue(key, out value)) - { - value = handler(key); - dictionary.Add(key, value); + value = handler(key); + dictionary.Add(key, value); - isNew = true; - } + isNew = true; } } - - return value; } - /// - /// Asynchronously adds a new key to a dictionary with a value generated by a handler if it doesn't exist, using a reader-writer lock. - /// - /// The type of keys. - /// The type of values. - /// The dictionary to operate on. - /// The reader-writer lock for synchronization. - /// The key to add or retrieve. - /// The asynchronous function to generate a value if the key is new. - /// The cancellation token to cancel the operation. - /// The existing or newly created value. - /// Thrown when , , or is null. - [CLSCompliant(false)] - public static async Task SafeAddAsync(this IDictionary dictionary, AsyncReaderWriterLock sync, TKey key, Func> handler, CancellationToken cancellationToken) - { - if (dictionary is null) - throw new ArgumentNullException(nameof(dictionary)); - - if (sync is null) - throw new ArgumentNullException(nameof(sync)); - - if (handler is null) - throw new ArgumentNullException(nameof(handler)); - - TValue value; - - using (await sync.ReaderLockAsync(cancellationToken)) - { - if (dictionary.TryGetValue(key, out value)) - return value; - } + return value; + } - value = await handler(key, cancellationToken); + /// + /// Asynchronously adds a new key to a dictionary with a value generated by a handler if it doesn't exist, using a reader-writer lock. + /// + /// The type of keys. + /// The type of values. + /// The dictionary to operate on. + /// The reader-writer lock for synchronization. + /// The key to add or retrieve. + /// The asynchronous function to generate a value if the key is new. + /// The cancellation token to cancel the operation. + /// The existing or newly created value. + /// Thrown when , , or is null. + [CLSCompliant(false)] + public static async Task SafeAddAsync(this IDictionary dictionary, AsyncReaderWriterLock sync, TKey key, Func> handler, CancellationToken cancellationToken) + { + if (dictionary is null) + throw new ArgumentNullException(nameof(dictionary)); - using var _ = await sync.WriterLockAsync(cancellationToken); + if (sync is null) + throw new ArgumentNullException(nameof(sync)); - if (dictionary.TryGetValue(key, out var temp)) - return temp; + if (handler is null) + throw new ArgumentNullException(nameof(handler)); - dictionary.Add(key, value); + TValue value; - return value; + using (await sync.ReaderLockAsync(cancellationToken)) + { + if (dictionary.TryGetValue(key, out value)) + return value; } - /// - /// Asynchronously adds a new key to a dictionary of task completion sources, ensuring only one task is created per key, using a reader-writer lock. - /// - /// The type of keys. - /// The type of values. - /// The dictionary mapping keys to task completion sources. - /// The reader-writer lock for synchronization. - /// The key to add or retrieve. - /// The asynchronous function to generate a value if the key is new. - /// The cancellation token to cancel the operation. - /// The task representing the value for the key. - /// Thrown when , , or is null. - [CLSCompliant(false)] - public static async Task SafeAddAsync(this IDictionary> dictionary, AsyncReaderWriterLock sync, TKey key, Func> handler, CancellationToken cancellationToken) - { - if (dictionary is null) - throw new ArgumentNullException(nameof(dictionary)); + value = await handler(key, cancellationToken); - if (sync is null) - throw new ArgumentNullException(nameof(sync)); + using var _ = await sync.WriterLockAsync(cancellationToken); - if (handler is null) - throw new ArgumentNullException(nameof(handler)); + if (dictionary.TryGetValue(key, out var temp)) + return temp; - async Task> InternalSafeAddAsync() - { - TaskCompletionSource source; + dictionary.Add(key, value); - using (await sync.ReaderLockAsync(cancellationToken)) - { - if (dictionary.TryGetValue(key, out source)) - return source.Task; - } + return value; + } - using (await sync.WriterLockAsync(cancellationToken)) - { - if (dictionary.TryGetValue(key, out source)) - return source.Task; + /// + /// Asynchronously adds a new key to a dictionary of task completion sources, ensuring only one task is created per key, using a reader-writer lock. + /// + /// The type of keys. + /// The type of values. + /// The dictionary mapping keys to task completion sources. + /// The reader-writer lock for synchronization. + /// The key to add or retrieve. + /// The asynchronous function to generate a value if the key is new. + /// The cancellation token to cancel the operation. + /// The task representing the value for the key. + /// Thrown when , , or is null. + [CLSCompliant(false)] + public static async Task SafeAddAsync(this IDictionary> dictionary, AsyncReaderWriterLock sync, TKey key, Func> handler, CancellationToken cancellationToken) + { + if (dictionary is null) + throw new ArgumentNullException(nameof(dictionary)); - source = new TaskCompletionSource(); - _ = Task.Factory.StartNew(async () => source.SetResult(await handler(key, cancellationToken))); + if (sync is null) + throw new ArgumentNullException(nameof(sync)); - dictionary.Add(key, source); - return source.Task; - } - } + if (handler is null) + throw new ArgumentNullException(nameof(handler)); - return await (await InternalSafeAddAsync()); - } - - /// - /// Asynchronously adds a new key to a dictionary of task completion sources, ensuring only one task is created per key, using synchronized locking. - /// - /// The type of keys. - /// The type of values. - /// The dictionary mapping keys to task completion sources. - /// The key to add or retrieve. - /// The asynchronous function to generate a value if the key is new. - /// The cancellation token to cancel the operation. - /// The task representing the value for the key. - /// Thrown when or is null. - public static Task SafeAddAsync(this IDictionary> dictionary, TKey key, Func> handler, CancellationToken cancellationToken) + async Task> InternalSafeAddAsync() { - if (dictionary is null) - throw new ArgumentNullException(nameof(dictionary)); - - if (handler is null) - throw new ArgumentNullException(nameof(handler)); - - var syncObj = (dictionary as ISynchronizedCollection)?.SyncRoot ?? (object)dictionary; - TaskCompletionSource source; - lock (syncObj) + using (await sync.ReaderLockAsync(cancellationToken)) { if (dictionary.TryGetValue(key, out source)) return source.Task; - - source = new(); - dictionary.Add(key, source); } - void remove() + using (await sync.WriterLockAsync(cancellationToken)) { - lock (syncObj) - dictionary.Remove(key); + if (dictionary.TryGetValue(key, out source)) + return source.Task; + + source = new TaskCompletionSource(); + _ = Task.Factory.StartNew(async () => source.SetResult(await handler(key, cancellationToken))); + + dictionary.Add(key, source); + return source.Task; } + } - try - { - handler(key, cancellationToken).ContinueWith(t => - { - if (t.IsFaulted || t.IsCanceled) - remove(); + return await (await InternalSafeAddAsync()); + } - source.TryCompleteFromCompletedTask(t); + /// + /// Asynchronously adds a new key to a dictionary of task completion sources, ensuring only one task is created per key, using synchronized locking. + /// + /// The type of keys. + /// The type of values. + /// The dictionary mapping keys to task completion sources. + /// The key to add or retrieve. + /// The asynchronous function to generate a value if the key is new. + /// The cancellation token to cancel the operation. + /// The task representing the value for the key. + /// Thrown when or is null. + public static Task SafeAddAsync(this IDictionary> dictionary, TKey key, Func> handler, CancellationToken cancellationToken) + { + if (dictionary is null) + throw new ArgumentNullException(nameof(dictionary)); - }, TaskContinuationOptions.ExecuteSynchronously); - } - catch - { - remove(); - throw; - } + if (handler is null) + throw new ArgumentNullException(nameof(handler)); - return source.Task; - } + var syncObj = (dictionary as ISynchronizedCollection)?.SyncRoot ?? (object)dictionary; - /// - /// Attempts to retrieve a value from a dictionary by key, returning the default value if not found. - /// - /// The type of keys. - /// The type of values. - /// The dictionary to query. - /// The key to look up. - /// The value if found; otherwise, the default value of . - /// Thrown when is null. - public static TValue TryGetValue(this IDictionary dict, TKey key) - //where V : class + TaskCompletionSource source; + + lock (syncObj) { - if (dict is null) - throw new ArgumentNullException(nameof(dict)); + if (dictionary.TryGetValue(key, out source)) + return source.Task; - dict.TryGetValue(key, out var value); - return value; + source = new(); + dictionary.Add(key, source); } - /// - /// Attempts to retrieve a value from a dictionary by key, returning null if not found (for value types). - /// - /// The type of keys. - /// The type of values, which must be a value type. - /// The dictionary to query. - /// The key to look up. - /// The value if found; otherwise, null. - /// Thrown when is null. - public static TValue? TryGetValue2(this IDictionary dict, TKey key) - where TValue : struct + void remove() { - if (dict is null) - throw new ArgumentNullException(nameof(dict)); - - if (dict.TryGetValue(key, out var value)) - return value; - else - return null; + lock (syncObj) + dictionary.Remove(key); } - /// - /// Attempts to retrieve a key from a pair set by value, returning the default value if not found. - /// - /// The type of keys. - /// The type of values. - /// The pair set to query. - /// The value to look up. - /// The key if found; otherwise, the default value of . - public static TKey TryGetKey(this PairSet pairSet, TValue value) + try { - pairSet.TryGetKey(value, out var key); - return key; - } + handler(key, cancellationToken).ContinueWith(t => + { + if (t.IsFaulted || t.IsCanceled) + remove(); - /// - /// Attempts to retrieve a key from a pair set by value, returning null if not found (for value-type keys). - /// - /// The type of keys, which must be a value type. - /// The type of values. - /// The pair set to query. - /// The value to look up. - /// The key if found; otherwise, null. - public static TKey? TryGetKey2(this PairSet pairSet, TValue value) - where TKey : struct + source.TryCompleteFromCompletedTask(t); + + }, TaskContinuationOptions.ExecuteSynchronously); + } + catch { - if (pairSet.TryGetKey(value, out var key)) - return key; - else - return null; + remove(); + throw; } - #endregion + return source.Task; + } - /// - /// Executes a function on a synchronized collection with thread-safe access. - /// - /// The type of the synchronized collection. - /// The type of the result. - /// The synchronized collection to operate on. - /// The function to execute. - /// The result of the function. - /// Thrown when or is null. - public static TResult SyncGet(this TCollection collection, Func func) - where TCollection : class, ISynchronizedCollection - { - if (collection is null) - throw new ArgumentNullException(nameof(collection)); + /// + /// Attempts to retrieve a value from a dictionary by key, returning the default value if not found. + /// + /// The type of keys. + /// The type of values. + /// The dictionary to query. + /// The key to look up. + /// The value if found; otherwise, the default value of . + /// Thrown when is null. + public static TValue TryGetValue(this IDictionary dict, TKey key) + //where V : class + { + if (dict is null) + throw new ArgumentNullException(nameof(dict)); - if (func is null) - throw new ArgumentNullException(nameof(func)); + dict.TryGetValue(key, out var value); + return value; + } - lock (collection.SyncRoot) - return func(collection); - } + /// + /// Attempts to retrieve a value from a dictionary by key, returning null if not found (for value types). + /// + /// The type of keys. + /// The type of values, which must be a value type. + /// The dictionary to query. + /// The key to look up. + /// The value if found; otherwise, null. + /// Thrown when is null. + public static TValue? TryGetValue2(this IDictionary dict, TKey key) + where TValue : struct + { + if (dict is null) + throw new ArgumentNullException(nameof(dict)); - /// - /// Executes an action on a synchronized collection with thread-safe access. - /// - /// The type of the synchronized collection. - /// The synchronized collection to operate on. - /// The action to execute. - /// Thrown when or is null. - public static void SyncDo(this TCollection collection, Action action) - where TCollection : class, ISynchronizedCollection - { - if (collection is null) - throw new ArgumentNullException(nameof(collection)); + if (dict.TryGetValue(key, out var value)) + return value; + else + return null; + } - if (action is null) - throw new ArgumentNullException(nameof(action)); + /// + /// Attempts to retrieve a key from a pair set by value, returning the default value if not found. + /// + /// The type of keys. + /// The type of values. + /// The pair set to query. + /// The value to look up. + /// The key if found; otherwise, the default value of . + public static TKey TryGetKey(this PairSet pairSet, TValue value) + { + pairSet.TryGetKey(value, out var key); + return key; + } - lock (collection.SyncRoot) - action(collection); - } + /// + /// Attempts to retrieve a key from a pair set by value, returning null if not found (for value-type keys). + /// + /// The type of keys, which must be a value type. + /// The type of values. + /// The pair set to query. + /// The value to look up. + /// The key if found; otherwise, null. + public static TKey? TryGetKey2(this PairSet pairSet, TValue value) + where TKey : struct + { + if (pairSet.TryGetKey(value, out var key)) + return key; + else + return null; + } - /// - /// Retrieves all keys in a synchronized dictionary that map to a specific value. - /// - /// The type of keys. - /// The type of values. - /// The synchronized dictionary to query. - /// The value to match. - /// An enumerable of keys associated with the specified value. - public static IEnumerable GetKeys(this SynchronizedDictionary dictionary, TValue value) - { - lock (dictionary.SyncRoot) - return ((IDictionary)dictionary).GetKeys(value); - } + #endregion - /// - /// Attempts to dequeue an item from a queue, returning null if the queue is empty (for reference types). - /// - /// The type of elements in the queue, which must be a reference type. - /// The queue to dequeue from. - /// The dequeued item if available; otherwise, null. - public static T TryDequeue(this Queue queue) - where T : class - { + /// + /// Executes a function on a synchronized collection with thread-safe access. + /// + /// The type of the synchronized collection. + /// The type of the result. + /// The synchronized collection to operate on. + /// The function to execute. + /// The result of the function. + /// Thrown when or is null. + public static TResult SyncGet(this TCollection collection, Func func) + where TCollection : class, ISynchronizedCollection + { + if (collection is null) + throw new ArgumentNullException(nameof(collection)); + + if (func is null) + throw new ArgumentNullException(nameof(func)); + + lock (collection.SyncRoot) + return func(collection); + } + + /// + /// Executes an action on a synchronized collection with thread-safe access. + /// + /// The type of the synchronized collection. + /// The synchronized collection to operate on. + /// The action to execute. + /// Thrown when or is null. + public static void SyncDo(this TCollection collection, Action action) + where TCollection : class, ISynchronizedCollection + { + if (collection is null) + throw new ArgumentNullException(nameof(collection)); + + if (action is null) + throw new ArgumentNullException(nameof(action)); + + lock (collection.SyncRoot) + action(collection); + } + + /// + /// Retrieves all keys in a synchronized dictionary that map to a specific value. + /// + /// The type of keys. + /// The type of values. + /// The synchronized dictionary to query. + /// The value to match. + /// An enumerable of keys associated with the specified value. + public static IEnumerable GetKeys(this SynchronizedDictionary dictionary, TValue value) + { + lock (dictionary.SyncRoot) + return ((IDictionary)dictionary).GetKeys(value); + } + + /// + /// Attempts to dequeue an item from a queue, returning null if the queue is empty (for reference types). + /// + /// The type of elements in the queue, which must be a reference type. + /// The queue to dequeue from. + /// The dequeued item if available; otherwise, null. + public static T TryDequeue(this Queue queue) + where T : class + { + return queue.IsEmpty() ? null : queue.Dequeue(); + } + + /// + /// Attempts to dequeue an item from a queue, returning null if the queue is empty (for value types). + /// + /// The type of elements in the queue, which must be a value type. + /// The queue to dequeue from. + /// The dequeued item if available; otherwise, null. + public static T? TryDequeue2(this Queue queue) + where T : struct + { + return queue.IsEmpty() ? (T?)null : queue.Dequeue(); + } + + /// + /// Attempts to dequeue an item from a synchronized queue, returning null if the queue is empty (for reference types). + /// + /// The type of elements in the queue, which must be a reference type. + /// The synchronized queue to dequeue from. + /// The dequeued item if available; otherwise, null. + public static T TryDequeue(this SynchronizedQueue queue) + where T : class + { + lock (queue.SyncRoot) return queue.IsEmpty() ? null : queue.Dequeue(); - } + } - /// - /// Attempts to dequeue an item from a queue, returning null if the queue is empty (for value types). - /// - /// The type of elements in the queue, which must be a value type. - /// The queue to dequeue from. - /// The dequeued item if available; otherwise, null. - public static T? TryDequeue2(this Queue queue) - where T : struct - { + /// + /// Attempts to dequeue an item from a synchronized queue, returning null if the queue is empty (for value types). + /// + /// The type of elements in the queue, which must be a value type. + /// The synchronized queue to dequeue from. + /// The dequeued item if available; otherwise, null. + public static T? TryDequeue2(this SynchronizedQueue queue) + where T : struct + { + lock (queue.SyncRoot) return queue.IsEmpty() ? (T?)null : queue.Dequeue(); - } + } - /// - /// Attempts to dequeue an item from a synchronized queue, returning null if the queue is empty (for reference types). - /// - /// The type of elements in the queue, which must be a reference type. - /// The synchronized queue to dequeue from. - /// The dequeued item if available; otherwise, null. - public static T TryDequeue(this SynchronizedQueue queue) - where T : class - { - lock (queue.SyncRoot) - return queue.IsEmpty() ? null : queue.Dequeue(); - } + /// + /// Attempts to peek at the next item in a queue, returning null if the queue is empty (for reference types). + /// + /// The type of elements in the queue, which must be a reference type. + /// The queue to peek into. + /// The next item if available; otherwise, null. + public static T TryPeek(this Queue queue) + where T : class + { + return queue.IsEmpty() ? null : queue.Peek(); + } - /// - /// Attempts to dequeue an item from a synchronized queue, returning null if the queue is empty (for value types). - /// - /// The type of elements in the queue, which must be a value type. - /// The synchronized queue to dequeue from. - /// The dequeued item if available; otherwise, null. - public static T? TryDequeue2(this SynchronizedQueue queue) - where T : struct - { - lock (queue.SyncRoot) - return queue.IsEmpty() ? (T?)null : queue.Dequeue(); - } + /// + /// Attempts to peek at the next item in a queue, returning null if the queue is empty (for value types). + /// + /// The type of elements in the queue, which must be a value type. + /// The queue to peek into. + /// The next item if available; otherwise, null. + public static T? TryPeek2(this Queue queue) + where T : struct + { + return queue.IsEmpty() ? (T?)null : queue.Peek(); + } - /// - /// Attempts to peek at the next item in a queue, returning null if the queue is empty (for reference types). - /// - /// The type of elements in the queue, which must be a reference type. - /// The queue to peek into. - /// The next item if available; otherwise, null. - public static T TryPeek(this Queue queue) - where T : class - { + /// + /// Attempts to peek at the next item in a synchronized queue, returning null if the queue is empty (for reference types). + /// + /// The type of elements in the queue, which must be a reference type. + /// The synchronized queue to peek into. + /// The next item if available; otherwise, null. + public static T TryPeek(this SynchronizedQueue queue) + where T : class + { + lock (queue.SyncRoot) return queue.IsEmpty() ? null : queue.Peek(); - } + } - /// - /// Attempts to peek at the next item in a queue, returning null if the queue is empty (for value types). - /// - /// The type of elements in the queue, which must be a value type. - /// The queue to peek into. - /// The next item if available; otherwise, null. - public static T? TryPeek2(this Queue queue) - where T : struct - { + /// + /// Attempts to peek at the next item in a synchronized queue, returning null if the queue is empty (for value types). + /// + /// The type of elements in the queue, which must be a value type. + /// The synchronized queue to peek into. + /// The next item if available; otherwise, null. + public static T? TryPeek2(this SynchronizedQueue queue) + where T : struct + { + lock (queue.SyncRoot) return queue.IsEmpty() ? (T?)null : queue.Peek(); - } + } - /// - /// Attempts to peek at the next item in a synchronized queue, returning null if the queue is empty (for reference types). - /// - /// The type of elements in the queue, which must be a reference type. - /// The synchronized queue to peek into. - /// The next item if available; otherwise, null. - public static T TryPeek(this SynchronizedQueue queue) - where T : class - { - lock (queue.SyncRoot) - return queue.IsEmpty() ? null : queue.Peek(); - } + /// + /// Returns the first element of a sequence, or a specified alternate value if the sequence is empty. + /// + /// The type of elements in the sequence. + /// The sequence to query. + /// The value to return if the sequence is empty. + /// The first element, or if the sequence is empty. + public static T FirstOr(this IEnumerable source, T alternate) + { + foreach (var t in source) + return t; - /// - /// Attempts to peek at the next item in a synchronized queue, returning null if the queue is empty (for value types). - /// - /// The type of elements in the queue, which must be a value type. - /// The synchronized queue to peek into. - /// The next item if available; otherwise, null. - public static T? TryPeek2(this SynchronizedQueue queue) - where T : struct - { - lock (queue.SyncRoot) - return queue.IsEmpty() ? (T?)null : queue.Peek(); - } + return alternate; + } - /// - /// Returns the first element of a sequence, or a specified alternate value if the sequence is empty. - /// - /// The type of elements in the sequence. - /// The sequence to query. - /// The value to return if the sequence is empty. - /// The first element, or if the sequence is empty. - public static T FirstOr(this IEnumerable source, T alternate) - { - foreach (var t in source) - return t; + /// + /// Returns the first element of a sequence, or null if the sequence is empty (for value types). + /// + /// The type of elements in the sequence, which must be a value type. + /// The sequence to query. + /// The first element, or null if the sequence is empty. + /// Thrown when is null. + public static T? FirstOr(this IEnumerable source) + where T : struct + { + if (source is null) + throw new ArgumentNullException(nameof(source)); - return alternate; - } + foreach (var t in source) + return t; - /// - /// Returns the first element of a sequence, or null if the sequence is empty (for value types). - /// - /// The type of elements in the sequence, which must be a value type. - /// The sequence to query. - /// The first element, or null if the sequence is empty. - /// Thrown when is null. - public static T? FirstOr(this IEnumerable source) - where T : struct + return null; + } + + /// + /// Returns the last element of a sequence, or null if the sequence is empty (for value types). + /// + /// The type of elements in the sequence, which must be a value type. + /// The sequence to query. + /// The last element, or null if the sequence is empty. + /// Thrown when is null. + public static T? LastOr(this IEnumerable source) + where T : struct + { + if (source is null) + throw new ArgumentNullException(nameof(source)); + + if (source is IList list) { - if (source is null) - throw new ArgumentNullException(nameof(source)); + var count = list.Count; - foreach (var t in source) - return t; + if (count > 0) + return list[count - 1]; return null; } - /// - /// Returns the last element of a sequence, or null if the sequence is empty (for value types). - /// - /// The type of elements in the sequence, which must be a value type. - /// The sequence to query. - /// The last element, or null if the sequence is empty. - /// Thrown when is null. - public static T? LastOr(this IEnumerable source) - where T : struct - { - if (source is null) - throw new ArgumentNullException(nameof(source)); - - if (source is IList list) - { - var count = list.Count; + T? last = null; - if (count > 0) - return list[count - 1]; + foreach (var t in source) + last = t; - return null; - } + return last; + } - T? last = null; + /// + /// Returns the element at a specified index in a sequence, or null if the index is out of range (for value types). + /// + /// The type of elements in the sequence, which must be a value type. + /// The sequence to query. + /// The zero-based index of the element to retrieve. + /// The element at the specified index, or null if the index is out of range. + /// Thrown when is null. + /// Thrown when is negative. + public static T? ElementAtOr(this IEnumerable source, int index) + where T : struct + { + if (source is null) + throw new ArgumentNullException(nameof(source)); - foreach (var t in source) - last = t; + if (index < 0) + throw new ArgumentOutOfRangeException(nameof(index)); - return last; + if (source is IList list) + { + if (index < list.Count) + return list[index]; } - - /// - /// Returns the element at a specified index in a sequence, or null if the index is out of range (for value types). - /// - /// The type of elements in the sequence, which must be a value type. - /// The sequence to query. - /// The zero-based index of the element to retrieve. - /// The element at the specified index, or null if the index is out of range. - /// Thrown when is null. - /// Thrown when is negative. - public static T? ElementAtOr(this IEnumerable source, int index) - where T : struct + else { - if (source is null) - throw new ArgumentNullException(nameof(source)); - - if (index < 0) - throw new ArgumentOutOfRangeException(nameof(index)); - - if (source is IList list) - { - if (index < list.Count) - return list[index]; - } - else + foreach (var i in source) { - foreach (var i in source) - { - if (index == 0) - return i; + if (index == 0) + return i; - --index; - } + --index; } - - return null; } - /// - /// Determines whether a sequence of characters is empty. - /// - /// The sequence of characters to check. - /// True if the sequence is null or empty; otherwise, false. - [Obsolete("Use StringHelper.IsEmpty.")] - public static bool IsEmpty(this IEnumerable source) - => source is null || !source.Any(); + return null; + } - /// - /// Determines whether a sequence is empty. - /// - /// The type of elements in the sequence. - /// The sequence to check. - /// True if the sequence is empty; otherwise, false. - public static bool IsEmpty(this IEnumerable source) - { - if (source is ICollection col) - return col.Count == 0; + /// + /// Determines whether a sequence of characters is empty. + /// + /// The sequence of characters to check. + /// True if the sequence is null or empty; otherwise, false. + [Obsolete("Use StringHelper.IsEmpty.")] + public static bool IsEmpty(this IEnumerable source) + => source is null || !source.Any(); - if (source is ICollection col2) - return col2.Count == 0; + /// + /// Determines whether a sequence is empty. + /// + /// The type of elements in the sequence. + /// The sequence to check. + /// True if the sequence is empty; otherwise, false. + public static bool IsEmpty(this IEnumerable source) + { + if (source is ICollection col) + return col.Count == 0; - if (source is IEnumerableEx ex) - return ex.Count == 0; + if (source is ICollection col2) + return col2.Count == 0; - return !source.Any(); - } + if (source is IEnumerableEx ex) + return ex.Count == 0; - /// - /// Determines whether a sequence is empty based on a predicate. - /// - /// The type of elements in the sequence. - /// The sequence to check. - /// The condition to test each element against. - /// True if no elements match the predicate; otherwise, false. - public static bool IsEmpty(this IEnumerable source, Func predicate) - { - return !source.Any(predicate); - } + return !source.Any(); + } - /// - /// Determines whether a collection is empty. - /// - /// The type of elements in the collection. - /// The collection to check. - /// True if the collection is empty; otherwise, false. - public static bool IsEmpty(this ICollection source) - { - return source.Count == 0; - } + /// + /// Determines whether a sequence is empty based on a predicate. + /// + /// The type of elements in the sequence. + /// The sequence to check. + /// The condition to test each element against. + /// True if no elements match the predicate; otherwise, false. + public static bool IsEmpty(this IEnumerable source, Func predicate) + { + return !source.Any(predicate); + } - /// - /// Determines whether an array is empty. - /// - /// The type of elements in the array. - /// The array to check. - /// True if the array is empty; otherwise, false. - /// Thrown when is null. - public static bool IsEmpty(this T[] source) - { - if (source is null) - throw new ArgumentNullException(nameof(source)); + /// + /// Determines whether a collection is empty. + /// + /// The type of elements in the collection. + /// The collection to check. + /// True if the collection is empty; otherwise, false. + public static bool IsEmpty(this ICollection source) + { + return source.Count == 0; + } - return source.Length == 0; - } + /// + /// Determines whether an array is empty. + /// + /// The type of elements in the array. + /// The array to check. + /// True if the array is empty; otherwise, false. + /// Thrown when is null. + public static bool IsEmpty(this T[] source) + { + if (source is null) + throw new ArgumentNullException(nameof(source)); + + return source.Length == 0; + } - #region BitArray methods + #region BitArray methods - /// - /// Converts a double value to its binary representation as an array of bits. - /// - /// The double value to convert. - /// An array of 64 bits representing the value. - public static bool[] ToBits(this double value) - { - return value.ToBits(64); - } + /// + /// Converts a double value to its binary representation as an array of bits. + /// + /// The double value to convert. + /// An array of 64 bits representing the value. + public static bool[] ToBits(this double value) + { + return value.ToBits(64); + } - /// - /// Converts a double value to a specified number of bits. - /// - /// The double value to convert. - /// The number of bits to return. - /// An array of bits representing the value. - public static bool[] ToBits(this double value, int count) - { - return value.ToBits(0, count); - } + /// + /// Converts a double value to a specified number of bits. + /// + /// The double value to convert. + /// The number of bits to return. + /// An array of bits representing the value. + public static bool[] ToBits(this double value, int count) + { + return value.ToBits(0, count); + } - /// - /// Converts a double value to a specified range of bits. - /// - /// The double value to convert. - /// The starting bit position (0-based). - /// The number of bits to return. - /// An array of bits representing the specified range. - public static bool[] ToBits(this double value, int startBit, int bitCount) - { - return value.AsRaw().ToBits(startBit, bitCount); - } + /// + /// Converts a double value to a specified range of bits. + /// + /// The double value to convert. + /// The starting bit position (0-based). + /// The number of bits to return. + /// An array of bits representing the specified range. + public static bool[] ToBits(this double value, int startBit, int bitCount) + { + return value.AsRaw().ToBits(startBit, bitCount); + } - /// - /// Converts a float value to its binary representation as an array of bits. - /// - /// The float value to convert. - /// An array of 32 bits representing the value. - public static bool[] ToBits(this float value) - { - return value.ToBits(32); - } + /// + /// Converts a float value to its binary representation as an array of bits. + /// + /// The float value to convert. + /// An array of 32 bits representing the value. + public static bool[] ToBits(this float value) + { + return value.ToBits(32); + } - /// - /// Converts a float value to a specified number of bits. - /// - /// The float value to convert. - /// The number of bits to return. - /// An array of bits representing the value. - public static bool[] ToBits(this float value, int bitCount) - { - return value.ToBits(0, bitCount); - } + /// + /// Converts a float value to a specified number of bits. + /// + /// The float value to convert. + /// The number of bits to return. + /// An array of bits representing the value. + public static bool[] ToBits(this float value, int bitCount) + { + return value.ToBits(0, bitCount); + } - /// - /// Converts a float value to a specified range of bits. - /// - /// The float value to convert. - /// The starting bit position (0-based). - /// The number of bits to return. - /// An array of bits representing the specified range. - public static bool[] ToBits(this float value, int startBit, int bitCount) - { - return value.AsRaw().ToBits(startBit, bitCount); - } + /// + /// Converts a float value to a specified range of bits. + /// + /// The float value to convert. + /// The starting bit position (0-based). + /// The number of bits to return. + /// An array of bits representing the specified range. + public static bool[] ToBits(this float value, int startBit, int bitCount) + { + return value.AsRaw().ToBits(startBit, bitCount); + } - /// - /// Converts a long value to its binary representation as an array of bits. - /// - /// The long value to convert. - /// An array of 64 bits representing the value. - public static bool[] ToBits(this long value) - { - return value.ToBits(64); - } + /// + /// Converts a long value to its binary representation as an array of bits. + /// + /// The long value to convert. + /// An array of 64 bits representing the value. + public static bool[] ToBits(this long value) + { + return value.ToBits(64); + } - /// - /// Converts a long value to a specified number of bits. - /// - /// The long value to convert. - /// The number of bits to return. - /// An array of bits representing the value. - public static bool[] ToBits(this long value, int bitCount) - { - //if (value > 2.Pow(bitCount - 1)) - // throw new ArgumentOutOfRangeException(nameof(value)); + /// + /// Converts a long value to a specified number of bits. + /// + /// The long value to convert. + /// The number of bits to return. + /// An array of bits representing the value. + public static bool[] ToBits(this long value, int bitCount) + { + //if (value > 2.Pow(bitCount - 1)) + // throw new ArgumentOutOfRangeException(nameof(value)); - return value.ToBits(0, bitCount); - } + return value.ToBits(0, bitCount); + } - /// - /// Converts a long value to a specified range of bits. - /// - /// The long value to convert. - /// The starting bit position (0-based). - /// The number of bits to return. - /// An array of bits representing the specified range. - public static bool[] ToBits(this long value, int startBit, int bitCount) - { - var ints = value.GetParts(); + /// + /// Converts a long value to a specified range of bits. + /// + /// The long value to convert. + /// The starting bit position (0-based). + /// The number of bits to return. + /// An array of bits representing the specified range. + public static bool[] ToBits(this long value, int startBit, int bitCount) + { + var ints = value.GetParts(); - var bits = new List(); + var bits = new List(); - if (startBit < 32) - bits.AddRange(ints[0].ToBits(startBit, bitCount.Min(32 - startBit))); + if (startBit < 32) + bits.AddRange(ints[0].ToBits(startBit, bitCount.Min(32 - startBit))); - if ((startBit + bitCount) > 32) - bits.AddRange(ints[1].ToBits((startBit - 32).Max(0), (bitCount - 32))); + if ((startBit + bitCount) > 32) + bits.AddRange(ints[1].ToBits((startBit - 32).Max(0), (bitCount - 32))); - return [.. bits]; - } + return [.. bits]; + } - /// - /// Converts an int value to its binary representation as an array of bits. - /// - /// The int value to convert. - /// An array of 32 bits representing the value. - public static bool[] ToBits(this int value) - { - return value.ToBits(32); - } + /// + /// Converts an int value to its binary representation as an array of bits. + /// + /// The int value to convert. + /// An array of 32 bits representing the value. + public static bool[] ToBits(this int value) + { + return value.ToBits(32); + } + + /// + /// Converts an int value to a specified number of bits. + /// + /// The int value to convert. + /// The number of bits to return. + /// An array of bits representing the value. + public static bool[] ToBits(this int value, int bitCount) + { + //if (value > 2.Pow(bitCount - 1)) + // throw new ArgumentOutOfRangeException(nameof(value)); - /// - /// Converts an int value to a specified number of bits. - /// - /// The int value to convert. - /// The number of bits to return. - /// An array of bits representing the value. - public static bool[] ToBits(this int value, int bitCount) - { - //if (value > 2.Pow(bitCount - 1)) - // throw new ArgumentOutOfRangeException(nameof(value)); + return value.ToBits(0, bitCount); + } - return value.ToBits(0, bitCount); - } + /// + /// Converts an int value to a specified range of bits. + /// + /// The int value to convert. + /// The starting bit position (0-based). + /// The number of bits to return. + /// An array of bits representing the specified range. + /// Thrown when is outside [0, 31] or is negative or exceeds 32 when added to . + public static bool[] ToBits(this int value, int startBit, int bitCount) + { + if (startBit > 31 || startBit < 0) + throw new ArgumentOutOfRangeException(nameof(startBit)); - /// - /// Converts an int value to a specified range of bits. - /// - /// The int value to convert. - /// The starting bit position (0-based). - /// The number of bits to return. - /// An array of bits representing the specified range. - /// Thrown when is outside [0, 31] or is negative or exceeds 32 when added to . - public static bool[] ToBits(this int value, int startBit, int bitCount) - { - if (startBit > 31 || startBit < 0) - throw new ArgumentOutOfRangeException(nameof(startBit)); + if (bitCount < 0) + throw new ArgumentOutOfRangeException(nameof(bitCount)); - if (bitCount < 0) - throw new ArgumentOutOfRangeException(nameof(bitCount)); + if ((startBit + bitCount) > 32) + throw new ArgumentOutOfRangeException(nameof(bitCount)); - if ((startBit + bitCount) > 32) - throw new ArgumentOutOfRangeException(nameof(bitCount)); + var bits = new bool[bitCount]; - var bits = new bool[bitCount]; + for (var i = 0; i < bitCount; i++) + bits[i] = value.GetBit(startBit + i); - for (var i = 0; i < bitCount; i++) - bits[i] = value.GetBit(startBit + i); + return bits; + } - return bits; - } + /// + /// Converts an array of bits to an integer starting from the beginning. + /// + /// The array of bits to convert. + /// The integer value represented by the bits. + public static int FromBits(this bool[] bits) + { + return bits.FromBits(0); + } - /// - /// Converts an array of bits to an integer starting from the beginning. - /// - /// The array of bits to convert. - /// The integer value represented by the bits. - public static int FromBits(this bool[] bits) - { - return bits.FromBits(0); - } + /// + /// Converts an array of bits to an integer starting from a specified position. + /// + /// The array of bits to convert. + /// The starting bit position (0-based). + /// The integer value represented by the bits. + public static int FromBits(this bool[] bits, int startBit) + { + return (int)bits.FromBits2(startBit); + } - /// - /// Converts an array of bits to an integer starting from a specified position. - /// - /// The array of bits to convert. - /// The starting bit position (0-based). - /// The integer value represented by the bits. - public static int FromBits(this bool[] bits, int startBit) - { - return (int)bits.FromBits2(startBit); - } + /// + /// Converts an array of bits to a long integer starting from the beginning. + /// + /// The array of bits to convert. + /// The long integer value represented by the bits. + public static long FromBits2(this bool[] bits) + { + return bits.FromBits(0); + } - /// - /// Converts an array of bits to a long integer starting from the beginning. - /// - /// The array of bits to convert. - /// The long integer value represented by the bits. - public static long FromBits2(this bool[] bits) - { - return bits.FromBits(0); - } + /// + /// Converts an array of bits to a long integer starting from a specified position. + /// + /// The array of bits to convert. + /// The starting bit position (0-based). + /// The long integer value represented by the bits. + /// Thrown when is null. + /// Thrown when is greater than or equal to the length of . + public static long FromBits2(this bool[] bits, int startBit) + { + if (bits is null) + throw new ArgumentNullException(nameof(bits)); - /// - /// Converts an array of bits to a long integer starting from a specified position. - /// - /// The array of bits to convert. - /// The starting bit position (0-based). - /// The long integer value represented by the bits. - /// Thrown when is null. - /// Thrown when is greater than or equal to the length of . - public static long FromBits2(this bool[] bits, int startBit) - { - if (bits is null) - throw new ArgumentNullException(nameof(bits)); + if (startBit >= bits.Length) + throw new ArgumentOutOfRangeException(nameof(startBit)); - if (startBit >= bits.Length) - throw new ArgumentOutOfRangeException(nameof(startBit)); + var value = 0L; - var value = 0L; + for (var i = 0; i < bits.Length; i++) + value = value.SetBit(i + startBit, bits[i]); - for (var i = 0; i < bits.Length; i++) - value = value.SetBit(i + startBit, bits[i]); + return value; + } - return value; - } + /// + /// Adds a range of bits to an existing . + /// + /// The to extend. + /// The bits to add. + /// Thrown when or is null. + public static void AddRange(this BitArray array, params bool[] bits) + { + if (array is null) + throw new ArgumentNullException(nameof(array)); - /// - /// Adds a range of bits to an existing . - /// - /// The to extend. - /// The bits to add. - /// Thrown when or is null. - public static void AddRange(this BitArray array, params bool[] bits) - { - if (array is null) - throw new ArgumentNullException(nameof(array)); + if (bits is null) + throw new ArgumentNullException(nameof(bits)); - if (bits is null) - throw new ArgumentNullException(nameof(bits)); + var arrayLength = array.Length; - var arrayLength = array.Length; + array.Length += bits.Length; - array.Length += bits.Length; + for (var i = 0; i < bits.Length; i++) + array[arrayLength + i] = bits[i]; + } - for (var i = 0; i < bits.Length; i++) - array[arrayLength + i] = bits[i]; - } + #endregion - #endregion + /// + /// A private implementation of that wraps an enumerable with a predefined count. + /// + /// The type of elements in the enumerable. + private sealed class EnumerableEx : SimpleEnumerable, IEnumerableEx + { + private readonly int _count; /// - /// A private implementation of that wraps an enumerable with a predefined count. + /// Initializes a new instance of the class. /// - /// The type of elements in the enumerable. - private sealed class EnumerableEx : SimpleEnumerable, IEnumerableEx + /// The underlying enumerable to wrap. + /// The predefined count of elements. + /// Thrown when is negative. + public EnumerableEx(IEnumerable enumerable, int count) + : base(enumerable.GetEnumerator) { - private readonly int _count; - - /// - /// Initializes a new instance of the class. - /// - /// The underlying enumerable to wrap. - /// The predefined count of elements. - /// Thrown when is negative. - public EnumerableEx(IEnumerable enumerable, int count) - : base(enumerable.GetEnumerator) - { - if (count < 0) - throw new ArgumentOutOfRangeException(nameof(count)); + if (count < 0) + throw new ArgumentOutOfRangeException(nameof(count)); - _count = count; - } - - /// - /// Gets the predefined count of elements. - /// - int IEnumerableEx.Count => _count; + _count = count; } /// - /// Converts an enumerable to an with a count determined by enumeration. + /// Gets the predefined count of elements. /// - /// The type of elements in the enumerable. - /// The enumerable to convert. - /// An with the specified count. - public static IEnumerableEx ToEx(this IEnumerable values) - { - return values.ToEx(values.Count()); - } + int IEnumerableEx.Count => _count; + } - /// - /// Converts an enumerable to an with a specified count. - /// - /// The type of elements in the enumerable. - /// The enumerable to convert. - /// The predefined count of elements. - /// An with the specified count. - public static IEnumerableEx ToEx(this IEnumerable values, int count) - { - return new EnumerableEx(values, count); - } + /// + /// Converts an enumerable to an with a count determined by enumeration. + /// + /// The type of elements in the enumerable. + /// The enumerable to convert. + /// An with the specified count. + public static IEnumerableEx ToEx(this IEnumerable values) + { + return values.ToEx(values.Count()); + } - /// - /// Converts a list to a synchronized list, or returns it if already synchronized. - /// - /// The type of elements in the list. - /// The list to synchronize. - /// A containing the list's elements, or null if is null. - public static SynchronizedList Sync(this IList list) - { - if (list is null) - return null; + /// + /// Converts an enumerable to an with a specified count. + /// + /// The type of elements in the enumerable. + /// The enumerable to convert. + /// The predefined count of elements. + /// An with the specified count. + public static IEnumerableEx ToEx(this IEnumerable values, int count) + { + return new EnumerableEx(values, count); + } - if (list is not SynchronizedList syncList) - { - syncList = [.. list]; - } + /// + /// Converts a list to a synchronized list, or returns it if already synchronized. + /// + /// The type of elements in the list. + /// The list to synchronize. + /// A containing the list's elements, or null if is null. + public static SynchronizedList Sync(this IList list) + { + if (list is null) + return null; - return syncList; + if (list is not SynchronizedList syncList) + { + syncList = [.. list]; } - /// - /// Converts a dictionary to a synchronized dictionary, or returns it if already synchronized. - /// - /// The type of keys in the dictionary. - /// The type of values in the dictionary. - /// The dictionary to synchronize. - /// A containing the dictionary's elements, or null if is null. - public static SynchronizedDictionary Sync(this IDictionary dict) - { - if (dict is null) - return null; + return syncList; + } - if (dict is not SynchronizedDictionary syncDict) - { - var typedDict = dict as Dictionary; - syncDict = new SynchronizedDictionary(typedDict?.Comparer); - syncDict.AddRange(dict); - } + /// + /// Converts a dictionary to a synchronized dictionary, or returns it if already synchronized. + /// + /// The type of keys in the dictionary. + /// The type of values in the dictionary. + /// The dictionary to synchronize. + /// A containing the dictionary's elements, or null if is null. + public static SynchronizedDictionary Sync(this IDictionary dict) + { + if (dict is null) + return null; - return syncDict; + if (dict is not SynchronizedDictionary syncDict) + { + var typedDict = dict as Dictionary; + syncDict = new SynchronizedDictionary(typedDict?.Comparer); + syncDict.AddRange(dict); } - /// - /// Converts a hash set to a synchronized set. - /// - /// The type of elements in the set. - /// The hash set to synchronize. - /// A containing the set's elements, or null if is null. - public static SynchronizedSet Sync(this HashSet list) - { - if (list is null) - return null; + return syncDict; + } - var syncList = new SynchronizedSet(); - syncList.AddRange(list); - return syncList; - } + /// + /// Converts a hash set to a synchronized set. + /// + /// The type of elements in the set. + /// The hash set to synchronize. + /// A containing the set's elements, or null if is null. + public static SynchronizedSet Sync(this HashSet list) + { + if (list is null) + return null; - /// - /// Filters a sequence based on a predicate that compares each element with its previous element. - /// - /// The type of elements in the sequence. - /// The sequence to filter. - /// The function to test each element against its previous element. - /// An enumerable of elements where the predicate returns true when compared to the previous element. - /// Thrown when or is null. - public static IEnumerable WhereWithPrevious(this IEnumerable source, Func predicate) - { - if (source is null) - throw new ArgumentNullException(nameof(source)); + var syncList = new SynchronizedSet(); + syncList.AddRange(list); + return syncList; + } - if (predicate is null) - throw new ArgumentNullException(nameof(predicate)); + /// + /// Filters a sequence based on a predicate that compares each element with its previous element. + /// + /// The type of elements in the sequence. + /// The sequence to filter. + /// The function to test each element against its previous element. + /// An enumerable of elements where the predicate returns true when compared to the previous element. + /// Thrown when or is null. + public static IEnumerable WhereWithPrevious(this IEnumerable source, Func predicate) + { + if (source is null) + throw new ArgumentNullException(nameof(source)); - using var iterator = source.GetEnumerator(); - if (!iterator.MoveNext()) - yield break; + if (predicate is null) + throw new ArgumentNullException(nameof(predicate)); - var previous = iterator.Current; - yield return previous; + using var iterator = source.GetEnumerator(); + if (!iterator.MoveNext()) + yield break; - while (iterator.MoveNext()) - { - var current = iterator.Current; + var previous = iterator.Current; + yield return previous; - if (!predicate(previous, current)) - continue; + while (iterator.MoveNext()) + { + var current = iterator.Current; - yield return current; - previous = current; - } + if (!predicate(previous, current)) + continue; + + yield return current; + previous = current; } + } - /// - /// Binds a source notifying list to a destination list, synchronizing additions, removals, insertions, and clear operations. - /// - /// The type of elements in the lists. - /// The source list that notifies changes. - /// The destination list to synchronize with. - /// Thrown when or is null. - public static void Bind(this INotifyList source, IList destination) - { - if (source is null) - throw new ArgumentNullException(nameof(source)); + /// + /// Binds a source notifying list to a destination list, synchronizing additions, removals, insertions, and clear operations. + /// + /// The type of elements in the lists. + /// The source list that notifies changes. + /// The destination list to synchronize with. + /// Thrown when or is null. + public static void Bind(this INotifyList source, IList destination) + { + if (source is null) + throw new ArgumentNullException(nameof(source)); - if (destination is null) - throw new ArgumentNullException(nameof(destination)); + if (destination is null) + throw new ArgumentNullException(nameof(destination)); - source.Added += destination.Add; - source.Removed += item => destination.Remove(item); - source.Inserted += destination.Insert; - source.Cleared += destination.Clear; + source.Added += destination.Add; + source.Removed += item => destination.Remove(item); + source.Inserted += destination.Insert; + source.Cleared += destination.Clear; - source.ForEach(destination.Add); - } + source.ForEach(destination.Add); + } - /// - /// Computes the Damerau-Levenshtein distance between two sequences, with a threshold to limit computation. - /// - /// The type of elements in the sequences, which must implement . - /// The first sequence. - /// The second sequence. - /// The maximum distance to compute; returns int.MaxValue if exceeded. - /// The Damerau-Levenshtein distance, or int.MaxValue if it exceeds the threshold. - /// Thrown when or is null. - // http://stackoverflow.com/a/9454016 - public static int DamerauLevenshteinDistance(T[] source, T[] target, int threshold) - where T : IEquatable - { - if (source is null) - throw new ArgumentNullException(nameof(source)); + /// + /// Computes the Damerau-Levenshtein distance between two sequences, with a threshold to limit computation. + /// + /// The type of elements in the sequences, which must implement . + /// The first sequence. + /// The second sequence. + /// The maximum distance to compute; returns int.MaxValue if exceeded. + /// The Damerau-Levenshtein distance, or int.MaxValue if it exceeds the threshold. + /// Thrown when or is null. + // http://stackoverflow.com/a/9454016 + public static int DamerauLevenshteinDistance(T[] source, T[] target, int threshold) + where T : IEquatable + { + if (source is null) + throw new ArgumentNullException(nameof(source)); - if (target is null) - throw new ArgumentNullException(nameof(target)); + if (target is null) + throw new ArgumentNullException(nameof(target)); - var length1 = source.Length; - var length2 = target.Length; + var length1 = source.Length; + var length2 = target.Length; - // Return trivial case - difference in string lengths exceeds threshold - if (Math.Abs(length1 - length2) > threshold) - return int.MaxValue; + // Return trivial case - difference in string lengths exceeds threshold + if (Math.Abs(length1 - length2) > threshold) + return int.MaxValue; - // Ensure arrays [i] / length1 use shorter length - if (length1 > length2) - { - (target, source) = (source, target); - (length1, length2) = (length2, length1); - } + // Ensure arrays [i] / length1 use shorter length + if (length1 > length2) + { + (target, source) = (source, target); + (length1, length2) = (length2, length1); + } - var maxi = length1; - var maxj = length2; + var maxi = length1; + var maxj = length2; - var dCurrent = new int[maxi + 1]; - var dMinus1 = new int[maxi + 1]; - var dMinus2 = new int[maxi + 1]; + var dCurrent = new int[maxi + 1]; + var dMinus1 = new int[maxi + 1]; + var dMinus2 = new int[maxi + 1]; - for (var i = 0; i <= maxi; i++) - dCurrent[i] = i; + for (var i = 0; i <= maxi; i++) + dCurrent[i] = i; - var jm1 = 0; + var jm1 = 0; - for (var j = 1; j <= maxj; j++) - { - // Rotate - var dSwap = dMinus2; - dMinus2 = dMinus1; - dMinus1 = dCurrent; - dCurrent = dSwap; - - // Initialize - var minDistance = int.MaxValue; - dCurrent[0] = j; - var im1 = 0; - var im2 = -1; - - for (var i = 1; i <= maxi; i++) - { - var cost = source[im1].Equals(target[jm1]) ? 0 : 1; + for (var j = 1; j <= maxj; j++) + { + // Rotate + var dSwap = dMinus2; + dMinus2 = dMinus1; + dMinus1 = dCurrent; + dCurrent = dSwap; - var del = dCurrent[im1] + 1; - var ins = dMinus1[i] + 1; - var sub = dMinus1[im1] + cost; + // Initialize + var minDistance = int.MaxValue; + dCurrent[0] = j; + var im1 = 0; + var im2 = -1; - // Fastest execution for min value of 3 integers - var min = (del > ins) ? (ins > sub ? sub : ins) : (del > sub ? sub : del); + for (var i = 1; i <= maxi; i++) + { + var cost = source[im1].Equals(target[jm1]) ? 0 : 1; - if (i > 1 && j > 1 && source[im2].Equals(target[jm1]) && source[im1].Equals(target[j - 2])) - min = Math.Min(min, dMinus2[im2] + cost); + var del = dCurrent[im1] + 1; + var ins = dMinus1[i] + 1; + var sub = dMinus1[im1] + cost; - dCurrent[i] = min; + // Fastest execution for min value of 3 integers + var min = (del > ins) ? (ins > sub ? sub : ins) : (del > sub ? sub : del); - if (min < minDistance) - minDistance = min; + if (i > 1 && j > 1 && source[im2].Equals(target[jm1]) && source[im1].Equals(target[j - 2])) + min = Math.Min(min, dMinus2[im2] + cost); - im1++; - im2++; - } + dCurrent[i] = min; - jm1++; + if (min < minDistance) + minDistance = min; - if (minDistance > threshold) - return int.MaxValue; + im1++; + im2++; } - var result = dCurrent[maxi]; - return result > threshold ? int.MaxValue : result; + jm1++; + + if (minDistance > threshold) + return int.MaxValue; } - /// - /// Converts a sequence to a set using the default equality comparer. - /// - /// The type of elements in the sequence. - /// The sequence to convert. - /// A set containing the unique elements from the sequence. - public static ISet ToSet(this IEnumerable values) - { + var result = dCurrent[maxi]; + return result > threshold ? int.MaxValue : result; + } + + /// + /// Converts a sequence to a set using the default equality comparer. + /// + /// The type of elements in the sequence. + /// The sequence to convert. + /// A set containing the unique elements from the sequence. + public static ISet ToSet(this IEnumerable values) + { #if NETSTANDARD2_0 - return new HashSet(values); + return new HashSet(values); #else - return values.ToHashSet(); + return values.ToHashSet(); #endif - } + } - /// - /// Converts a sequence of strings to a case-insensitive set. - /// - /// The sequence of strings to convert. - /// A set containing the unique strings, ignoring case. - public static ISet ToIgnoreCaseSet(this IEnumerable values) - { + /// + /// Converts a sequence of strings to a case-insensitive set. + /// + /// The sequence of strings to convert. + /// A set containing the unique strings, ignoring case. + public static ISet ToIgnoreCaseSet(this IEnumerable values) + { #if NETSTANDARD2_0 - return new HashSet(values, StringComparer.InvariantCultureIgnoreCase); + return new HashSet(values, StringComparer.InvariantCultureIgnoreCase); #else - return values.ToHashSet(StringComparer.InvariantCultureIgnoreCase); + return values.ToHashSet(StringComparer.InvariantCultureIgnoreCase); #endif - } + } - /// - /// Splits a sequence into batches of a specified size. - /// - /// The type of elements in the sequence. - /// The sequence to batch. - /// The size of each batch. - /// An enumerable of arrays, each containing up to elements. - public static IEnumerable Batch(this IEnumerable source, int size) - { + /// + /// Splits a sequence into batches of a specified size. + /// + /// The type of elements in the sequence. + /// The sequence to batch. + /// The size of each batch. + /// An enumerable of arrays, each containing up to elements. + public static IEnumerable Batch(this IEnumerable source, int size) + { #if NETSTANDARD2_0 - return Batch(source, size, source => [.. source], () => false); + return Batch(source, size, source => [.. source], () => false); #else - return source.Chunk(size); + return source.Chunk(size); #endif - } + } - /// - /// Splits a sequence into batches with custom result selection and stopping condition. - /// - /// The type of elements in the sequence. - /// The type of the result for each batch. - /// The sequence to batch. - /// The maximum size of each batch. - /// The function to transform each batch into a result. - /// The function to determine if batching should stop prematurely. - /// An enumerable of results, each representing a batch. - public static IEnumerable Batch(this IEnumerable source, int size, - Func, TResult> resultSelector, Func needStop) - { - TSource[] bucket = null; - var count = 0; + /// + /// Splits a sequence into batches with custom result selection and stopping condition. + /// + /// The type of elements in the sequence. + /// The type of the result for each batch. + /// The sequence to batch. + /// The maximum size of each batch. + /// The function to transform each batch into a result. + /// The function to determine if batching should stop prematurely. + /// An enumerable of results, each representing a batch. + public static IEnumerable Batch(this IEnumerable source, int size, + Func, TResult> resultSelector, Func needStop) + { + TSource[] bucket = null; + var count = 0; - foreach (var item in source) - { - bucket ??= new TSource[size]; + foreach (var item in source) + { + bucket ??= new TSource[size]; - bucket[count++] = item; + bucket[count++] = item; - // The bucket is fully buffered before it's yielded - if (count != size) - { - if (needStop?.Invoke() != true) - continue; - } + // The bucket is fully buffered before it's yielded + if (count != size) + { + if (needStop?.Invoke() != true) + continue; + } - // Select is necessary so bucket contents are streamed too - yield return resultSelector(bucket); + // Select is necessary so bucket contents are streamed too + yield return resultSelector(bucket); - bucket = null; - count = 0; - } + bucket = null; + count = 0; + } - // Return the last bucket with all remaining elements - if (bucket != null && count > 0) - { - Array.Resize(ref bucket, count); - yield return resultSelector(bucket); - } + // Return the last bucket with all remaining elements + if (bucket != null && count > 0) + { + Array.Resize(ref bucket, count); + yield return resultSelector(bucket); } + } - /// - /// Appends a single value to the end of a sequence. - /// - /// The type of elements in the sequence. - /// The sequence to append to. - /// The value to append. - /// An enumerable with the value appended. - public static IEnumerable Append2(this IEnumerable values, T value) - => Enumerable.Append(values, value); + /// + /// Appends a single value to the end of a sequence. + /// + /// The type of elements in the sequence. + /// The sequence to append to. + /// The value to append. + /// An enumerable with the value appended. + public static IEnumerable Append2(this IEnumerable values, T value) + => Enumerable.Append(values, value); - /// - /// Asynchronously flattens a sequence of tasks that each return a sequence into a single sequence. - /// - /// The type of elements in the source sequence. - /// The type of elements in the resulting sequence. - /// The source sequence of elements. - /// The asynchronous function to transform each element into a sequence. - /// A task that resolves to a flattened sequence of results. - // https://stackoverflow.com/a/35874937 - public static async Task> SelectManyAsync(this IEnumerable enumeration, Func>> func) - => (await Task.WhenAll(enumeration.Select(func))).SelectMany(s => s); + /// + /// Asynchronously flattens a sequence of tasks that each return a sequence into a single sequence. + /// + /// The type of elements in the source sequence. + /// The type of elements in the resulting sequence. + /// The source sequence of elements. + /// The asynchronous function to transform each element into a sequence. + /// A task that resolves to a flattened sequence of results. + // https://stackoverflow.com/a/35874937 + public static async Task> SelectManyAsync(this IEnumerable enumeration, Func>> func) + => (await Task.WhenAll(enumeration.Select(func))).SelectMany(s => s); - /// - /// Converts a value tuple to a key-value pair. - /// - /// The type of the key. - /// The type of the value. - /// The value tuple to convert. - /// A containing the tuple's key and value. - public static KeyValuePair ToPair(this (TKey key, TValue value) _) - => new(_.key, _.value); + /// + /// Converts a value tuple to a key-value pair. + /// + /// The type of the key. + /// The type of the value. + /// The value tuple to convert. + /// A containing the tuple's key and value. + public static KeyValuePair ToPair(this (TKey key, TValue value) _) + => new(_.key, _.value); - /// - /// Adds a value tuple as a key-value pair to a dictionary. - /// - /// The type of the key. - /// The type of the value. - /// The dictionary to add to. - /// The value tuple to add. - public static void Add(this IDictionary dict, (TKey key, TValue value) tuple) - => dict.Add(tuple.ToPair()); + /// + /// Adds a value tuple as a key-value pair to a dictionary. + /// + /// The type of the key. + /// The type of the value. + /// The dictionary to add to. + /// The value tuple to add. + public static void Add(this IDictionary dict, (TKey key, TValue value) tuple) + => dict.Add(tuple.ToPair()); - /// - /// Applies an action to each element in a sequence. - /// - /// The type of elements in the sequence. - /// The sequence to iterate over. - /// The action to apply to each element. - /// Thrown when or is null. - public static void ForEach(this IEnumerable source, Action action) - { - if (source is null) - throw new ArgumentNullException(nameof(source)); + /// + /// Applies an action to each element in a sequence. + /// + /// The type of elements in the sequence. + /// The sequence to iterate over. + /// The action to apply to each element. + /// Thrown when or is null. + public static void ForEach(this IEnumerable source, Action action) + { + if (source is null) + throw new ArgumentNullException(nameof(source)); - if (action is null) - throw new ArgumentNullException(nameof(action)); + if (action is null) + throw new ArgumentNullException(nameof(action)); - foreach (var item in source) - action(item); - } + foreach (var item in source) + action(item); + } - /// - /// Generates all possible permutations of values selected from keys. - /// - /// The type of keys. - /// The type of values. - /// The sequence of keys. - /// The function to select possible values for each key. - /// An enumerable of arrays representing all permutations. - /// Thrown when or is null. - // https://stackoverflow.com/a/27328512 - public static IEnumerable Permutations(this IEnumerable keys, Func> selector) - { - if (keys is null) - throw new ArgumentNullException(nameof(keys)); + /// + /// Generates all possible permutations of values selected from keys. + /// + /// The type of keys. + /// The type of values. + /// The sequence of keys. + /// The function to select possible values for each key. + /// An enumerable of arrays representing all permutations. + /// Thrown when or is null. + // https://stackoverflow.com/a/27328512 + public static IEnumerable Permutations(this IEnumerable keys, Func> selector) + { + if (keys is null) + throw new ArgumentNullException(nameof(keys)); - if (selector is null) - throw new ArgumentNullException(nameof(selector)); + if (selector is null) + throw new ArgumentNullException(nameof(selector)); - var keyArray = keys.ToArray(); + var keyArray = keys.ToArray(); - if (keyArray.Length < 1) - yield break; + if (keyArray.Length < 1) + yield break; + + static IEnumerable Permutations(TKey[] keys, int index, Func> selector, TValue[] values) + { + var key = keys[index]; - static IEnumerable Permutations(TKey[] keys, int index, Func> selector, TValue[] values) + foreach (var value in selector(key)) { - var key = keys[index]; + values[index] = value; - foreach (var value in selector(key)) + if (index < keys.Length - 1) { - values[index] = value; - - if (index < keys.Length - 1) - { - foreach (var array in Permutations(keys, index + 1, selector, values)) - yield return array; - } - else - { - // Clone the array - yield return values.ToArray(); - } + foreach (var array in Permutations(keys, index + 1, selector, values)) + yield return array; + } + else + { + // Clone the array + yield return values.ToArray(); } } + } - var values = new TValue[keyArray.Length]; + var values = new TValue[keyArray.Length]; - foreach (var array in Permutations(keyArray, 0, selector, values)) - yield return array; - } + foreach (var array in Permutations(keyArray, 0, selector, values)) + yield return array; + } - /// - /// Returns the single element of a sequence if it contains exactly one element, otherwise returns the default value. - /// - /// The type of elements in the sequence. - /// The sequence to query. - /// The single element if the sequence has exactly one; otherwise, the default value of . - public static T SingleWhenOnly(this IEnumerable source) - { - if (source is ICollection coll) - return coll.Count == 1 ? coll.First() : default; - else - return source.Count() == 1 ? source.First() : default; - } + /// + /// Returns the single element of a sequence if it contains exactly one element, otherwise returns the default value. + /// + /// The type of elements in the sequence. + /// The sequence to query. + /// The single element if the sequence has exactly one; otherwise, the default value of . + public static T SingleWhenOnly(this IEnumerable source) + { + if (source is ICollection coll) + return coll.Count == 1 ? coll.First() : default; + else + return source.Count() == 1 ? source.First() : default; + } #if NETSTANDARD2_0 - /// - /// Returns all elements of a sequence except the last specified number of elements. - /// - /// The type of elements in the sequence. - /// The sequence to process. - /// The number of elements to skip from the end. - /// An enumerable with the last elements omitted. - public static IEnumerable SkipLast(this IEnumerable source, int count) - => source.Take(source.Count() - count); + /// + /// Returns all elements of a sequence except the last specified number of elements. + /// + /// The type of elements in the sequence. + /// The sequence to process. + /// The number of elements to skip from the end. + /// An enumerable with the last elements omitted. + public static IEnumerable SkipLast(this IEnumerable source, int count) + => source.Take(source.Count() - count); - /// - /// Clears all elements from a concurrent queue. - /// - /// The type of elements in the queue. - /// The concurrent queue to clear. - /// Thrown when is null. - // .NET Standard 2.0 doesn't has Clear - public static void Clear(this System.Collections.Concurrent.ConcurrentQueue queue) - { - if (queue is null) - throw new ArgumentNullException(nameof(queue)); + /// + /// Clears all elements from a concurrent queue. + /// + /// The type of elements in the queue. + /// The concurrent queue to clear. + /// Thrown when is null. + // .NET Standard 2.0 doesn't has Clear + public static void Clear(this System.Collections.Concurrent.ConcurrentQueue queue) + { + if (queue is null) + throw new ArgumentNullException(nameof(queue)); - while (queue.TryDequeue(out _)) { } - } + while (queue.TryDequeue(out _)) { } + } #endif - /// - /// Counts the number of elements in a non-generic sequence. - /// - /// The sequence to count. - /// The number of elements in the sequence. - public static int Count2(this IEnumerable source) - { - if (source is IList list) - return list.Count; - else if (source is ICollection c) - return c.Count; - else - return source.Cast().Count(); - } + /// + /// Counts the number of elements in a non-generic sequence. + /// + /// The sequence to count. + /// The number of elements in the sequence. + public static int Count2(this IEnumerable source) + { + if (source is IList list) + return list.Count; + else if (source is ICollection c) + return c.Count; + else + return source.Cast().Count(); } } diff --git a/Collections/IBlockingQueue.cs b/Collections/IBlockingQueue.cs index 3c456ce4..f39ed8a3 100644 --- a/Collections/IBlockingQueue.cs +++ b/Collections/IBlockingQueue.cs @@ -1,81 +1,80 @@ -namespace Ecng.Collections +namespace Ecng.Collections; + +/// +/// Represents a blocking queue interface for managing items in a thread-safe manner. +/// +/// The type of elements in the queue. +public interface IBlockingQueue { /// - /// Represents a blocking queue interface for managing items in a thread-safe manner. + /// Gets the number of elements contained in the queue. /// - /// The type of elements in the queue. - public interface IBlockingQueue - { - /// - /// Gets the number of elements contained in the queue. - /// - int Count { get; } + int Count { get; } - /// - /// Gets or sets the maximum number of elements that the queue can hold. - /// - int MaxSize { get; set; } + /// + /// Gets or sets the maximum number of elements that the queue can hold. + /// + int MaxSize { get; set; } - /// - /// Gets a value indicating whether the queue is closed. - /// - bool IsClosed { get; } + /// + /// Gets a value indicating whether the queue is closed. + /// + bool IsClosed { get; } - /// - /// Opens the queue, enabling enqueuing and dequeuing operations. - /// - void Open(); + /// + /// Opens the queue, enabling enqueuing and dequeuing operations. + /// + void Open(); - /// - /// Closes the queue to prevent further enqueuing operations. - /// - void Close(); + /// + /// Closes the queue to prevent further enqueuing operations. + /// + void Close(); - /// - /// Blocks the current thread until the queue becomes empty. - /// - void WaitUntilEmpty(); + /// + /// Blocks the current thread until the queue becomes empty. + /// + void WaitUntilEmpty(); - /// - /// Adds an item to the end of the queue. - /// - /// The item to add. - /// If set to true, the item is enqueued even if the queue is at its maximum capacity. - void Enqueue(T item, bool force = false); + /// + /// Adds an item to the end of the queue. + /// + /// The item to add. + /// If set to true, the item is enqueued even if the queue is at its maximum capacity. + void Enqueue(T item, bool force = false); - /// - /// Removes and returns the object at the beginning of the queue. - /// - /// The object that is removed from the beginning of the queue. - T Dequeue(); + /// + /// Removes and returns the object at the beginning of the queue. + /// + /// The object that is removed from the beginning of the queue. + T Dequeue(); - /// - /// Attempts to remove and return the object at the beginning of the queue. - /// - /// When this method returns, contains the object removed from the beginning of the queue, if the operation succeeded. - /// If set to true, the operation exits if the queue is closed. - /// If set to true, blocks until an item is available. - /// true if an object was successfully removed; otherwise, false. - bool TryDequeue(out T value, bool exitOnClose = true, bool block = true); + /// + /// Attempts to remove and return the object at the beginning of the queue. + /// + /// When this method returns, contains the object removed from the beginning of the queue, if the operation succeeded. + /// If set to true, the operation exits if the queue is closed. + /// If set to true, blocks until an item is available. + /// true if an object was successfully removed; otherwise, false. + bool TryDequeue(out T value, bool exitOnClose = true, bool block = true); - /// - /// Returns the object at the beginning of the queue without removing it. - /// - /// The object at the beginning of the queue. - T Peek(); + /// + /// Returns the object at the beginning of the queue without removing it. + /// + /// The object at the beginning of the queue. + T Peek(); - /// - /// Attempts to return the object at the beginning of the queue without removing it. - /// - /// When this method returns, contains the object at the beginning of the queue, if the operation succeeded. - /// If set to true, the operation exits if the queue is closed. - /// If set to true, blocks until an item is available. - /// true if an object was successfully retrieved; otherwise, false. - bool TryPeek(out T value, bool exitOnClose = true, bool block = true); + /// + /// Attempts to return the object at the beginning of the queue without removing it. + /// + /// When this method returns, contains the object at the beginning of the queue, if the operation succeeded. + /// If set to true, the operation exits if the queue is closed. + /// If set to true, blocks until an item is available. + /// true if an object was successfully retrieved; otherwise, false. + bool TryPeek(out T value, bool exitOnClose = true, bool block = true); - /// - /// Removes all objects from the queue. - /// - void Clear(); - } + /// + /// Removes all objects from the queue. + /// + void Clear(); } \ No newline at end of file diff --git a/Collections/ICollectionEx.cs b/Collections/ICollectionEx.cs index 291eaf78..9fa00bf2 100644 --- a/Collections/ICollectionEx.cs +++ b/Collections/ICollectionEx.cs @@ -1,42 +1,41 @@ -namespace Ecng.Collections -{ - using System; - using System.Collections.Generic; +namespace Ecng.Collections; + +using System; +using System.Collections.Generic; +/// +/// Provides an extended set of methods and events for collections of a specified type. +/// +/// The type of elements in the collection. +public interface ICollectionEx : ICollection +{ /// - /// Provides an extended set of methods and events for collections of a specified type. + /// Occurs when a range of elements is added. /// - /// The type of elements in the collection. - public interface ICollectionEx : ICollection - { - /// - /// Occurs when a range of elements is added. - /// - event Action> AddedRange; + event Action> AddedRange; - /// - /// Occurs when a range of elements is removed. - /// - event Action> RemovedRange; + /// + /// Occurs when a range of elements is removed. + /// + event Action> RemovedRange; - /// - /// Adds a range of elements to the collection. - /// - /// The elements to add. - void AddRange(IEnumerable items); + /// + /// Adds a range of elements to the collection. + /// + /// The elements to add. + void AddRange(IEnumerable items); - /// - /// Removes a range of elements from the collection. - /// - /// The elements to remove. - void RemoveRange(IEnumerable items); + /// + /// Removes a range of elements from the collection. + /// + /// The elements to remove. + void RemoveRange(IEnumerable items); - /// - /// Removes a specified number of elements starting at a certain index. - /// - /// The starting index. - /// The number of elements to remove. - /// The number of removed elements. - int RemoveRange(int index, int count); - } + /// + /// Removes a specified number of elements starting at a certain index. + /// + /// The starting index. + /// The number of elements to remove. + /// The number of removed elements. + int RemoveRange(int index, int count); } \ No newline at end of file diff --git a/Collections/IEnumerableEx.cs b/Collections/IEnumerableEx.cs index 90938298..c6045a70 100644 --- a/Collections/IEnumerableEx.cs +++ b/Collections/IEnumerableEx.cs @@ -1,24 +1,23 @@ -namespace Ecng.Collections -{ - using System.Collections; - using System.Collections.Generic; +namespace Ecng.Collections; - /// - /// Extended version of that provides the total number of elements. - /// - public interface IEnumerableEx : IEnumerable - { - /// - /// Gets the total number of elements. - /// - int Count { get; } - } +using System.Collections; +using System.Collections.Generic; +/// +/// Extended version of that provides the total number of elements. +/// +public interface IEnumerableEx : IEnumerable +{ /// - /// Extended version of that provides the total number of elements. + /// Gets the total number of elements. /// - /// The type of the element. - public interface IEnumerableEx : IEnumerable, IEnumerableEx - { - } + int Count { get; } +} + +/// +/// Extended version of that provides the total number of elements. +/// +/// The type of the element. +public interface IEnumerableEx : IEnumerable, IEnumerableEx +{ } \ No newline at end of file diff --git a/Collections/IListEx.cs b/Collections/IListEx.cs index b5519c54..42e02f99 100644 --- a/Collections/IListEx.cs +++ b/Collections/IListEx.cs @@ -1,12 +1,11 @@ -namespace Ecng.Collections -{ - using System.Collections.Generic; +namespace Ecng.Collections; + +using System.Collections.Generic; - /// - /// Provides an extended set of methods and events for lists of a specified type. - /// - /// The type of elements in the list. - public interface IListEx : IList, ICollectionEx - { - } +/// +/// Provides an extended set of methods and events for lists of a specified type. +/// +/// The type of elements in the list. +public interface IListEx : IList, ICollectionEx +{ } \ No newline at end of file diff --git a/Collections/INotifyList.cs b/Collections/INotifyList.cs index 62f82d6b..2aea09da 100644 --- a/Collections/INotifyList.cs +++ b/Collections/INotifyList.cs @@ -1,78 +1,77 @@ -namespace Ecng.Collections -{ - using System; - using System.Collections.Generic; +namespace Ecng.Collections; + +using System; +using System.Collections.Generic; +/// +/// Represents a collection that notifies about its changes. +/// +/// The type of elements in the collection. +public interface INotifyCollection : ICollection +{ /// - /// Represents a collection that notifies about its changes. + /// Occurs before adding an item, allowing cancellation. /// - /// The type of elements in the collection. - public interface INotifyCollection : ICollection - { - /// - /// Occurs before adding an item, allowing cancellation. - /// - event Func Adding; + event Func Adding; - /// - /// Occurs after an item has been added. - /// - event Action Added; - - /// - /// Occurs before removing an item, allowing cancellation. - /// - event Func Removing; - - /// - /// Occurs before removing an item at a specific index, allowing cancellation. - /// - event Func RemovingAt; + /// + /// Occurs after an item has been added. + /// + event Action Added; - /// - /// Occurs after an item has been removed. - /// - event Action Removed; + /// + /// Occurs before removing an item, allowing cancellation. + /// + event Func Removing; - /// - /// Occurs before clearing the collection, allowing cancellation. - /// - event Func Clearing; + /// + /// Occurs before removing an item at a specific index, allowing cancellation. + /// + event Func RemovingAt; - /// - /// Occurs after the collection has been cleared. - /// - event Action Cleared; + /// + /// Occurs after an item has been removed. + /// + event Action Removed; - /// - /// Occurs before inserting an item at a specified index, allowing cancellation. - /// - event Func Inserting; + /// + /// Occurs before clearing the collection, allowing cancellation. + /// + event Func Clearing; - /// - /// Occurs after an item has been inserted at a specific index. - /// - event Action Inserted; + /// + /// Occurs after the collection has been cleared. + /// + event Action Cleared; - /// - /// Occurs when a change happens in the collection. - /// - event Action Changed; - } + /// + /// Occurs before inserting an item at a specified index, allowing cancellation. + /// + event Func Inserting; /// - /// Represents a list that notifies about its changes. + /// Occurs after an item has been inserted at a specific index. /// - /// The type of elements in the list. - public interface INotifyList : INotifyCollection, IList - { - } + event Action Inserted; /// - /// Represents an extended list that notifies about its changes. + /// Occurs when a change happens in the collection. /// - /// The type of elements in the extended list. - public interface INotifyListEx : INotifyList, IListEx - { - } + event Action Changed; +} + +/// +/// Represents a list that notifies about its changes. +/// +/// The type of elements in the list. +public interface INotifyList : INotifyCollection, IList +{ +} + +/// +/// Represents an extended list that notifies about its changes. +/// +/// The type of elements in the extended list. +public interface INotifyListEx : INotifyList, IListEx +{ } \ No newline at end of file diff --git a/Collections/ISynchronizedCollection.cs b/Collections/ISynchronizedCollection.cs index fbbfa129..438ad9f1 100644 --- a/Collections/ISynchronizedCollection.cs +++ b/Collections/ISynchronizedCollection.cs @@ -1,24 +1,23 @@ -namespace Ecng.Collections -{ - using System.Collections.Generic; - using Ecng.Common; +namespace Ecng.Collections; - /// - /// Represents a collection that can be synchronized using a . - /// - public interface ISynchronizedCollection - { - /// - /// Gets the synchronization object used for thread-safe operations. - /// - SyncObject SyncRoot { get; } - } +using System.Collections.Generic; +using Ecng.Common; +/// +/// Represents a collection that can be synchronized using a . +/// +public interface ISynchronizedCollection +{ /// - /// Represents a generic synchronized collection. + /// Gets the synchronization object used for thread-safe operations. /// - /// The type of elements in the collection. - public interface ISynchronizedCollection : ISynchronizedCollection, ICollection - { - } + SyncObject SyncRoot { get; } +} + +/// +/// Represents a generic synchronized collection. +/// +/// The type of elements in the collection. +public interface ISynchronizedCollection : ISynchronizedCollection, ICollection +{ } \ No newline at end of file diff --git a/Collections/KeyedCollection.cs b/Collections/KeyedCollection.cs index 8ed8502c..d3c27849 100644 --- a/Collections/KeyedCollection.cs +++ b/Collections/KeyedCollection.cs @@ -1,255 +1,254 @@ -namespace Ecng.Collections +namespace Ecng.Collections; + +using System; +using System.Collections; +using System.Collections.Generic; + +/// +/// Represents an abstract dictionary-backed collection that manages key-value pairs with customizable behavior. +/// +/// The type of keys in the collection. +/// The type of values in the collection. +[Serializable] +public abstract class KeyedCollection(IDictionary innerDictionary) : IDictionary { - using System; - using System.Collections; - using System.Collections.Generic; - /// - /// Represents an abstract dictionary-backed collection that manages key-value pairs with customizable behavior. + /// Initializes a new instance of the class with a default dictionary. /// - /// The type of keys in the collection. - /// The type of values in the collection. - [Serializable] - public abstract class KeyedCollection(IDictionary innerDictionary) : IDictionary + protected KeyedCollection() + : this(new Dictionary()) { - /// - /// Initializes a new instance of the class with a default dictionary. - /// - protected KeyedCollection() - : this(new Dictionary()) - { - } + } - /// - /// Initializes a new instance of the class with a specified key comparer. - /// - /// The equality comparer for keys, or null to use the default comparer. - protected KeyedCollection(IEqualityComparer comparer) - : this(new Dictionary(comparer)) - { - } + /// + /// Initializes a new instance of the class with a specified key comparer. + /// + /// The equality comparer for keys, or null to use the default comparer. + protected KeyedCollection(IEqualityComparer comparer) + : this(new Dictionary(comparer)) + { + } - /// - /// Gets the underlying dictionary that stores the key-value pairs. - /// - protected IDictionary InnerDictionary { get; } = innerDictionary ?? throw new ArgumentNullException(nameof(innerDictionary)); - - /// - /// Adds the specified key-value pair to the collection. - /// - /// The key to add. - /// The value to add. - /// Thrown when the key-value pair cannot be added, as determined by . - /// Thrown when is null and the underlying dictionary does not permit null keys. - public virtual void Add(TKey key, TValue value) - { - if (!CanAdd(key, value)) - throw new ArgumentException(); + /// + /// Gets the underlying dictionary that stores the key-value pairs. + /// + protected IDictionary InnerDictionary { get; } = innerDictionary ?? throw new ArgumentNullException(nameof(innerDictionary)); - OnAdding(key, value); - InnerDictionary.Add(key, value); - OnAdded(key, value); - } + /// + /// Adds the specified key-value pair to the collection. + /// + /// The key to add. + /// The value to add. + /// Thrown when the key-value pair cannot be added, as determined by . + /// Thrown when is null and the underlying dictionary does not permit null keys. + public virtual void Add(TKey key, TValue value) + { + if (!CanAdd(key, value)) + throw new ArgumentException(); - /// - /// Gets or sets the value associated with the specified key. - /// - /// The key whose value is to be retrieved or set. - /// The value associated with the specified key. - /// Thrown when getting a value and the key is not found in the collection. - /// Thrown when setting a value and is null, if the underlying dictionary does not permit null keys. - public virtual TValue this[TKey key] - { - get => InnerDictionary[key]; - set - { - OnSetting(key, value); - InnerDictionary[key] = value; - OnSetted(key, value); - } - } + OnAdding(key, value); + InnerDictionary.Add(key, value); + OnAdded(key, value); + } - /// - /// Removes all items from the collection. - /// - public virtual void Clear() + /// + /// Gets or sets the value associated with the specified key. + /// + /// The key whose value is to be retrieved or set. + /// The value associated with the specified key. + /// Thrown when getting a value and the key is not found in the collection. + /// Thrown when setting a value and is null, if the underlying dictionary does not permit null keys. + public virtual TValue this[TKey key] + { + get => InnerDictionary[key]; + set { - OnClearing(); - InnerDictionary.Clear(); - OnCleared(); + OnSetting(key, value); + InnerDictionary[key] = value; + OnSetted(key, value); } + } - /// - /// Removes the value with the specified key from the collection. - /// - /// The key of the element to remove. - /// True if the element was found and removed; otherwise, false. - public virtual bool Remove(TKey key) - { - if (InnerDictionary.TryGetValue(key, out var value)) - { - OnRemoving(key, value); - var retVal = InnerDictionary.Remove(key); - OnRemoved(key, value); - return retVal; - } - else - return false; - } + /// + /// Removes all items from the collection. + /// + public virtual void Clear() + { + OnClearing(); + InnerDictionary.Clear(); + OnCleared(); + } - /// - /// Attempts to retrieve the value associated with the specified key. - /// - /// The key to locate. - /// When this method returns, contains the value associated with the key if found; otherwise, the default value of . - /// True if the key was found; otherwise, false. - public virtual bool TryGetValue(TKey key, out TValue value) + /// + /// Removes the value with the specified key from the collection. + /// + /// The key of the element to remove. + /// True if the element was found and removed; otherwise, false. + public virtual bool Remove(TKey key) + { + if (InnerDictionary.TryGetValue(key, out var value)) { - return InnerDictionary.TryGetValue(key, out value); + OnRemoving(key, value); + var retVal = InnerDictionary.Remove(key); + OnRemoved(key, value); + return retVal; } + else + return false; + } - /// - /// Gets the number of key-value pairs in the collection. - /// - public virtual int Count => InnerDictionary.Count; + /// + /// Attempts to retrieve the value associated with the specified key. + /// + /// The key to locate. + /// When this method returns, contains the value associated with the key if found; otherwise, the default value of . + /// True if the key was found; otherwise, false. + public virtual bool TryGetValue(TKey key, out TValue value) + { + return InnerDictionary.TryGetValue(key, out value); + } - /// - /// Returns an enumerator that iterates through the collection. - /// - /// An that can be used to iterate through the collection. - public virtual IEnumerator> GetEnumerator() - { - return InnerDictionary.GetEnumerator(); - } + /// + /// Gets the number of key-value pairs in the collection. + /// + public virtual int Count => InnerDictionary.Count; - /// - /// Called before adding a key-value pair to the collection. - /// - /// The key being added. - /// The value being added. - /// This method is intended for derived classes to implement custom logic before an addition occurs. - protected virtual void OnAdding(TKey key, TValue value) { } - - /// - /// Called after a key-value pair has been added to the collection. - /// - /// The key that was added. - /// The value that was added. - /// This method is intended for derived classes to implement custom logic after an addition occurs. - protected virtual void OnAdded(TKey key, TValue value) { } - - /// - /// Called before setting a new value for an existing key. - /// - /// The key being updated. - /// The new value to set. - /// This method is intended for derived classes to implement custom logic before a value is updated. - protected virtual void OnSetting(TKey key, TValue value) { } - - /// - /// Called after a new value has been set for an existing key. - /// - /// The key that was updated. - /// The new value that was set. - /// This method is intended for derived classes to implement custom logic after a value is updated. - protected virtual void OnSetted(TKey key, TValue value) { } - - /// - /// Called before clearing all items from the collection. - /// - /// This method is intended for derived classes to implement custom logic before clearing occurs. - protected virtual void OnClearing() {} - - /// - /// Called after all items have been cleared from the collection. - /// - /// This method is intended for derived classes to implement custom logic after clearing occurs. - protected virtual void OnCleared() {} - - /// - /// Called before removing a key-value pair from the collection. - /// - /// The key being removed. - /// The value being removed. - /// This method is intended for derived classes to implement custom logic before a removal occurs. - protected virtual void OnRemoving(TKey key, TValue value) { } - - /// - /// Called after a key-value pair has been removed from the collection. - /// - /// The key that was removed. - /// The value that was removed. - /// This method is intended for derived classes to implement custom logic after a removal occurs. - protected virtual void OnRemoved(TKey key, TValue value) { } - - /// - /// Determines whether a key-value pair can be added to the collection. - /// - /// The key to check. - /// The value to check. - /// True if the pair can be added; otherwise, false. - /// This method is intended for derived classes to enforce custom addition constraints. The default implementation always returns true. - protected virtual bool CanAdd(TKey key, TValue value) => true; - - /// - /// Gets a collection containing the keys in the dictionary. - /// - public ICollection Keys => InnerDictionary.Keys; - - /// - /// Gets a collection containing the values in the dictionary. - /// - public ICollection Values => InnerDictionary.Values; - - /// - /// Gets a value indicating whether the collection is read-only. - /// - bool ICollection>.IsReadOnly => InnerDictionary.IsReadOnly; - - /// - /// Determines whether the collection contains an element with the specified key. - /// - /// The key to locate. - /// True if the key is found; otherwise, false. - public bool ContainsKey(TKey key) => InnerDictionary.ContainsKey(key); - - /// - /// Adds a key-value pair to the collection. - /// - /// The key-value pair to add. - /// Thrown when the pair cannot be added, as determined by . - public void Add(KeyValuePair item) => Add(item.Key, item.Value); - - /// - /// Determines whether the collection contains a specific key-value pair. - /// - /// The key-value pair to locate. - /// True if the pair is found; otherwise, false. - /// This implementation only checks the presence of the key, not the value equality. - public bool Contains(KeyValuePair item) => ContainsKey(item.Key); - - /// - /// Copies the elements of the collection to an array, starting at the specified index. - /// - /// The destination array to copy to. - /// The zero-based index in the array at which copying begins. - /// Thrown when is null. - /// Thrown when is negative. - /// Thrown when the destination array is too small to accommodate the elements. - public void CopyTo(KeyValuePair[] array, int arrayIndex) => InnerDictionary.CopyTo(array, arrayIndex); - - /// - /// Removes a specific key-value pair from the collection. - /// - /// The key-value pair to remove. - /// True if the pair was found and removed; otherwise, false. - /// This implementation only considers the key for removal, ignoring the value in . - public bool Remove(KeyValuePair item) => Remove(item.Key); - - /// - /// Returns an enumerator that iterates through the collection (non-generic version). - /// - /// An that can be used to iterate through the collection. - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + /// + /// Returns an enumerator that iterates through the collection. + /// + /// An that can be used to iterate through the collection. + public virtual IEnumerator> GetEnumerator() + { + return InnerDictionary.GetEnumerator(); } + + /// + /// Called before adding a key-value pair to the collection. + /// + /// The key being added. + /// The value being added. + /// This method is intended for derived classes to implement custom logic before an addition occurs. + protected virtual void OnAdding(TKey key, TValue value) { } + + /// + /// Called after a key-value pair has been added to the collection. + /// + /// The key that was added. + /// The value that was added. + /// This method is intended for derived classes to implement custom logic after an addition occurs. + protected virtual void OnAdded(TKey key, TValue value) { } + + /// + /// Called before setting a new value for an existing key. + /// + /// The key being updated. + /// The new value to set. + /// This method is intended for derived classes to implement custom logic before a value is updated. + protected virtual void OnSetting(TKey key, TValue value) { } + + /// + /// Called after a new value has been set for an existing key. + /// + /// The key that was updated. + /// The new value that was set. + /// This method is intended for derived classes to implement custom logic after a value is updated. + protected virtual void OnSetted(TKey key, TValue value) { } + + /// + /// Called before clearing all items from the collection. + /// + /// This method is intended for derived classes to implement custom logic before clearing occurs. + protected virtual void OnClearing() {} + + /// + /// Called after all items have been cleared from the collection. + /// + /// This method is intended for derived classes to implement custom logic after clearing occurs. + protected virtual void OnCleared() {} + + /// + /// Called before removing a key-value pair from the collection. + /// + /// The key being removed. + /// The value being removed. + /// This method is intended for derived classes to implement custom logic before a removal occurs. + protected virtual void OnRemoving(TKey key, TValue value) { } + + /// + /// Called after a key-value pair has been removed from the collection. + /// + /// The key that was removed. + /// The value that was removed. + /// This method is intended for derived classes to implement custom logic after a removal occurs. + protected virtual void OnRemoved(TKey key, TValue value) { } + + /// + /// Determines whether a key-value pair can be added to the collection. + /// + /// The key to check. + /// The value to check. + /// True if the pair can be added; otherwise, false. + /// This method is intended for derived classes to enforce custom addition constraints. The default implementation always returns true. + protected virtual bool CanAdd(TKey key, TValue value) => true; + + /// + /// Gets a collection containing the keys in the dictionary. + /// + public ICollection Keys => InnerDictionary.Keys; + + /// + /// Gets a collection containing the values in the dictionary. + /// + public ICollection Values => InnerDictionary.Values; + + /// + /// Gets a value indicating whether the collection is read-only. + /// + bool ICollection>.IsReadOnly => InnerDictionary.IsReadOnly; + + /// + /// Determines whether the collection contains an element with the specified key. + /// + /// The key to locate. + /// True if the key is found; otherwise, false. + public bool ContainsKey(TKey key) => InnerDictionary.ContainsKey(key); + + /// + /// Adds a key-value pair to the collection. + /// + /// The key-value pair to add. + /// Thrown when the pair cannot be added, as determined by . + public void Add(KeyValuePair item) => Add(item.Key, item.Value); + + /// + /// Determines whether the collection contains a specific key-value pair. + /// + /// The key-value pair to locate. + /// True if the pair is found; otherwise, false. + /// This implementation only checks the presence of the key, not the value equality. + public bool Contains(KeyValuePair item) => ContainsKey(item.Key); + + /// + /// Copies the elements of the collection to an array, starting at the specified index. + /// + /// The destination array to copy to. + /// The zero-based index in the array at which copying begins. + /// Thrown when is null. + /// Thrown when is negative. + /// Thrown when the destination array is too small to accommodate the elements. + public void CopyTo(KeyValuePair[] array, int arrayIndex) => InnerDictionary.CopyTo(array, arrayIndex); + + /// + /// Removes a specific key-value pair from the collection. + /// + /// The key-value pair to remove. + /// True if the pair was found and removed; otherwise, false. + /// This implementation only considers the key for removal, ignoring the value in . + public bool Remove(KeyValuePair item) => Remove(item.Key); + + /// + /// Returns an enumerator that iterates through the collection (non-generic version). + /// + /// An that can be used to iterate through the collection. + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } \ No newline at end of file diff --git a/Collections/OrderedPriorityQueue.cs b/Collections/OrderedPriorityQueue.cs index 2d3bc5b7..81465548 100644 --- a/Collections/OrderedPriorityQueue.cs +++ b/Collections/OrderedPriorityQueue.cs @@ -1,265 +1,264 @@ -namespace Ecng.Collections +namespace Ecng.Collections; + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +/// +/// Represents a priority queue that sorts elements based on a specified key. +/// +/// +/// The type used to determine the sort order of elements. +/// +/// +/// The type of the values stored in the queue. +/// +[Obsolete("Use PriorityQueue.")] +public class OrderedPriorityQueue : ICollection>, IQueue> { - using System; - using System.Collections; - using System.Collections.Generic; - using System.Linq; + private readonly SortedDictionary> _dictionary; /// - /// Represents a priority queue that sorts elements based on a specified key. + /// Initializes a new instance of the class. /// - /// - /// The type used to determine the sort order of elements. - /// - /// - /// The type of the values stored in the queue. - /// - [Obsolete("Use PriorityQueue.")] - public class OrderedPriorityQueue : ICollection>, IQueue> + public OrderedPriorityQueue() { - private readonly SortedDictionary> _dictionary; + _dictionary = []; + } - /// - /// Initializes a new instance of the class. - /// - public OrderedPriorityQueue() - { - _dictionary = []; - } + /// + /// Initializes a new instance of the class using the specified comparer. + /// + /// The comparer used to sort the keys. + public OrderedPriorityQueue(IComparer comparer) + { + _dictionary = new SortedDictionary>(comparer); + } - /// - /// Initializes a new instance of the class using the specified comparer. - /// - /// The comparer used to sort the keys. - public OrderedPriorityQueue(IComparer comparer) - { - _dictionary = new SortedDictionary>(comparer); - } + #region Priority queue operations - #region Priority queue operations + /// + /// Enqueues element into priority queue + /// + /// element priority + /// element value + public void Enqueue(TPriority priority, TValue value) + { + _dictionary.SafeAdd(priority).Enqueue(value); + Count++; + } - /// - /// Enqueues element into priority queue - /// - /// element priority - /// element value - public void Enqueue(TPriority priority, TValue value) - { - _dictionary.SafeAdd(priority).Enqueue(value); - Count++; - } + /// + /// Dequeues element with minimum priority and return its priority and value as + /// + /// priority and value of the dequeued element + /// + /// Method throws if priority queue is empty + /// + public KeyValuePair Dequeue() + { + if (IsEmpty) + throw new InvalidOperationException("Priority queue is empty"); - /// - /// Dequeues element with minimum priority and return its priority and value as - /// - /// priority and value of the dequeued element - /// - /// Method throws if priority queue is empty - /// - public KeyValuePair Dequeue() - { - if (IsEmpty) - throw new InvalidOperationException("Priority queue is empty"); + var first = _dictionary.First(); - var first = _dictionary.First(); + var item = first.Value.Dequeue(); - var item = first.Value.Dequeue(); + if (first.Value.Count == 0) + _dictionary.Remove(first.Key); - if (first.Value.Count == 0) - _dictionary.Remove(first.Key); + Count--; - Count--; + return new KeyValuePair(first.Key, item); + } - return new KeyValuePair(first.Key, item); - } + /// + /// Dequeues element with minimum priority and return its priority and value as + /// + /// priority and value of the dequeued element + /// + /// Method throws if priority queue is empty + /// + public TValue DequeueValue() + { + return Dequeue().Value; + } - /// - /// Dequeues element with minimum priority and return its priority and value as - /// - /// priority and value of the dequeued element - /// - /// Method throws if priority queue is empty - /// - public TValue DequeueValue() - { - return Dequeue().Value; - } + /// + /// Returns priority and value of the element with minimun priority, without removing it from the queue + /// + /// priority and value of the element with minimum priority + /// + /// Method throws if priority queue is empty + /// + public KeyValuePair Peek() + { + if (IsEmpty) + throw new InvalidOperationException("Priority queue is empty"); - /// - /// Returns priority and value of the element with minimun priority, without removing it from the queue - /// - /// priority and value of the element with minimum priority - /// - /// Method throws if priority queue is empty - /// - public KeyValuePair Peek() - { - if (IsEmpty) - throw new InvalidOperationException("Priority queue is empty"); + var first = _dictionary.First(); + var item = first.Value.Peek(); - var first = _dictionary.First(); - var item = first.Value.Peek(); + return new KeyValuePair(first.Key, item); + } - return new KeyValuePair(first.Key, item); - } + /// + /// Returns priority and value of the element with minimun priority, without removing it from the queue + /// + /// priority and value of the element with minimum priority + /// + /// Method throws if priority queue is empty + /// + public TValue PeekValue() + { + return Peek().Value; + } - /// - /// Returns priority and value of the element with minimun priority, without removing it from the queue - /// - /// priority and value of the element with minimum priority - /// - /// Method throws if priority queue is empty - /// - public TValue PeekValue() - { - return Peek().Value; - } + /// + /// Gets whether priority queue is empty + /// + public bool IsEmpty => Count == 0; - /// - /// Gets whether priority queue is empty - /// - public bool IsEmpty => Count == 0; + #endregion - #endregion + #region ICollection> implementation - #region ICollection> implementation + /// + /// Enqueus element into priority queue + /// + /// element to add + public void Add(KeyValuePair item) + { + Enqueue(item.Key, item.Value); + } - /// - /// Enqueus element into priority queue - /// - /// element to add - public void Add(KeyValuePair item) - { - Enqueue(item.Key, item.Value); - } + /// + /// Clears the collection + /// + public void Clear() + { + _dictionary.Clear(); + Count = 0; + } - /// - /// Clears the collection - /// - public void Clear() - { - _dictionary.Clear(); - Count = 0; - } + /// + /// Determines whether the priority queue contains a specific element + /// + /// The object to locate in the priority queue + /// true if item is found in the priority queue; otherwise, false. + public bool Contains(KeyValuePair item) + { + if (!_dictionary.TryGetValue(item.Key, out var dic)) + return false; - /// - /// Determines whether the priority queue contains a specific element - /// - /// The object to locate in the priority queue - /// true if item is found in the priority queue; otherwise, false. - public bool Contains(KeyValuePair item) - { - if (!_dictionary.TryGetValue(item.Key, out var dic)) - return false; + return dic.Contains(item.Value); + } - return dic.Contains(item.Value); - } + /// + /// Gets number of elements in the priority queue + /// + public int Count { get; private set; } - /// - /// Gets number of elements in the priority queue - /// - public int Count { get; private set; } - - /// - /// Copies the elements of the priority queue to an Array, starting at a particular Array index. - /// - /// The one-dimensional Array that is the destination of the elements copied from the priority queue. The Array must have zero-based indexing. - /// The zero-based index in array at which copying begins. - /// - /// It is not guaranteed that items will be copied in the sorted order. - /// - public void CopyTo(KeyValuePair[] array, int arrayIndex) - { - AllItems.ToArray().CopyTo(array, arrayIndex); - } + /// + /// Copies the elements of the priority queue to an Array, starting at a particular Array index. + /// + /// The one-dimensional Array that is the destination of the elements copied from the priority queue. The Array must have zero-based indexing. + /// The zero-based index in array at which copying begins. + /// + /// It is not guaranteed that items will be copied in the sorted order. + /// + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + AllItems.ToArray().CopyTo(array, arrayIndex); + } - /// - /// Gets a value indicating whether the collection is read-only. - /// - /// - /// For priority queue this property returns false. - /// - public bool IsReadOnly => false; - - /// - /// Removes the first occurrence of a specific object from the priority queue. - /// - /// The object to remove from the . - /// true if item was successfully removed from the priority queue. - /// This method returns false if item is not found in the collection. - public bool Remove(KeyValuePair item) - { - return Remove(item.Key, [item.Value]); - } + /// + /// Gets a value indicating whether the collection is read-only. + /// + /// + /// For priority queue this property returns false. + /// + public bool IsReadOnly => false; - /// - /// Removes the range of elements from the priority queue. - /// - /// The range of elements to remove from the priority queue. - public void RemoveRange(IEnumerable> items) - { - var groups = items.GroupBy(i => i.Key, i => i.Value); + /// + /// Removes the first occurrence of a specific object from the priority queue. + /// + /// The object to remove from the . + /// true if item was successfully removed from the priority queue. + /// This method returns false if item is not found in the collection. + public bool Remove(KeyValuePair item) + { + return Remove(item.Key, [item.Value]); + } - foreach (var g in groups) - { - Remove(g.Key, [.. g]); - } - } + /// + /// Removes the range of elements from the priority queue. + /// + /// The range of elements to remove from the priority queue. + public void RemoveRange(IEnumerable> items) + { + var groups = items.GroupBy(i => i.Key, i => i.Value); - private bool Remove(TPriority key, ICollection items) + foreach (var g in groups) { - if (!_dictionary.TryGetValue(key, out var queue)) - return false; + Remove(g.Key, [.. g]); + } + } - _dictionary.Remove(key); + private bool Remove(TPriority key, ICollection items) + { + if (!_dictionary.TryGetValue(key, out var queue)) + return false; - while (queue.Count > 0) - { - var queueItem = queue.Dequeue(); + _dictionary.Remove(key); - if (items.Contains(queueItem)) - { - items.Remove(queueItem); - Count--; - continue; - } + while (queue.Count > 0) + { + var queueItem = queue.Dequeue(); - _dictionary.SafeAdd(key).Enqueue(queueItem); + if (items.Contains(queueItem)) + { + items.Remove(queueItem); + Count--; + continue; } - return true; + _dictionary.SafeAdd(key).Enqueue(queueItem); } - /// - /// Returns an enumerator that iterates through the collection. - /// - /// Enumerator - /// - /// Returned enumerator does not iterate elements in sorted order. - public IEnumerator> GetEnumerator() - { - return AllItems.GetEnumerator(); - } - - /// - /// Returns an enumerator that iterates through the collection. - /// - /// Enumerator - /// - /// Returned enumerator does not iterate elements in sorted order. - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + return true; + } - private IEnumerable> AllItems - { - get { return _dictionary.SelectMany(d => d.Value.Select(v => new KeyValuePair(d.Key, v))); } - } + /// + /// Returns an enumerator that iterates through the collection. + /// + /// Enumerator + /// + /// Returned enumerator does not iterate elements in sorted order. + public IEnumerator> GetEnumerator() + { + return AllItems.GetEnumerator(); + } - #endregion + /// + /// Returns an enumerator that iterates through the collection. + /// + /// Enumerator + /// + /// Returned enumerator does not iterate elements in sorted order. + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } - void IQueue>.Enqueue(KeyValuePair item) => - Enqueue(item.Key, item.Value); + private IEnumerable> AllItems + { + get { return _dictionary.SelectMany(d => d.Value.Select(v => new KeyValuePair(d.Key, v))); } } + + #endregion + + void IQueue>.Enqueue(KeyValuePair item) => + Enqueue(item.Key, item.Value); } \ No newline at end of file diff --git a/Collections/PairSet.cs b/Collections/PairSet.cs index 10d841a7..775a0465 100644 --- a/Collections/PairSet.cs +++ b/Collections/PairSet.cs @@ -1,215 +1,214 @@ -namespace Ecng.Collections +namespace Ecng.Collections; + +using System; +using System.Collections.Generic; + +/// +/// Represents a collection of key-value pairs that supports bidirectional lookup by key or value. +/// +/// The type of keys in the collection. +/// The type of values in the collection. +[Serializable] +public sealed class PairSet : KeyedCollection { - using System; - using System.Collections.Generic; - - /// - /// Represents a collection of key-value pairs that supports bidirectional lookup by key or value. - /// - /// The type of keys in the collection. - /// The type of values in the collection. - [Serializable] - public sealed class PairSet : KeyedCollection - { - #region Private Fields - - private readonly Dictionary _values; - - #endregion - - /// - /// Initializes a new instance of the class with default comparers. - /// - public PairSet() - { - _values = []; - } - - /// - /// Initializes a new instance of the class with a specified key comparer. - /// - /// The equality comparer for keys, or null to use the default comparer. - public PairSet(IEqualityComparer comparer) - : base(comparer) - { - _values = []; - } - - /// - /// Initializes a new instance of the class with specified key and value comparers. - /// - /// The equality comparer for keys, or null to use the default comparer. - /// The equality comparer for values, or null to use the default comparer. - public PairSet(IEqualityComparer keyComparer, IEqualityComparer valueComparer) - : base(keyComparer) - { - _values = new Dictionary(valueComparer); - } - - #region Item - - /// - /// Gets the key associated with the specified value. - /// - /// The value to look up. - /// The key associated with the specified value. - /// Thrown when the value is not found in the collection. - public TKey this[TValue value] => _values[value]; - - #endregion - - #region GetKey - - /// - /// Retrieves the key associated with the specified value. - /// - /// The value to look up. - /// The key associated with the specified value. - /// Thrown when the value is not found in the collection. - public TKey GetKey(TValue value) - { - return this[value]; - } - - #endregion - - #region GetValue - - /// - /// Retrieves the value associated with the specified key. - /// - /// The key to look up. - /// The value associated with the specified key. - /// Thrown when the key is not found in the collection. - public TValue GetValue(TKey key) - { - return base[key]; - } - - #endregion - - /// - /// Sets a new key for an existing value, replacing any previous association. - /// - /// The value to associate with the new key. - /// The new key to associate with the value. - public void SetKey(TValue value, TKey key) - { - RemoveByValue(value); - Add(key, value); - } - - #region SetValue - - /// - /// Sets a new value for an existing key, updating the internal value-to-key mapping. - /// - /// The key whose value is to be updated. - /// The new value to associate with the key. - public void SetValue(TKey key, TValue value) - { - base[key] = value; - } - - #endregion - - /// - /// Determines whether a key-value pair can be added to the collection. - /// - /// The key to check. - /// The value to check. - /// True if the value is not already associated with a key; otherwise, false. - protected override bool CanAdd(TKey key, TValue value) - { - return !_values.ContainsKey(value); - } - - #region KeyedCollection Members - - /// - /// Called before adding a key-value pair to the collection, updating the value-to-key mapping. - /// - /// The key being added. - /// The value being added. - protected override void OnAdding(TKey key, TValue value) - { - _values.Add(value, key); - } - - /// - /// Called before setting a new value for an existing key, updating the value-to-key mapping. - /// - /// The key being updated. - /// The new value to set. - protected override void OnSetting(TKey key, TValue value) - { - _values[value] = key; - } - - /// - /// Called before clearing the collection, clearing the value-to-key mapping. - /// - protected override void OnClearing() - { - _values.Clear(); - } - - /// - /// Called before removing a key-value pair, updating the value-to-key mapping. - /// - /// The key being removed. - /// The value being removed. - protected override void OnRemoving(TKey key, TValue value) - { - _values.Remove(value); - } - - #endregion - - /// - /// Attempts to retrieve the key associated with a specified value. - /// - /// The value to look up. - /// When this method returns, contains the key if found; otherwise, the default value of . - /// True if the value was found; otherwise, false. - public bool TryGetKey(TValue value, out TKey key) - { - return _values.TryGetValue(value, out key); - } - - /// - /// Attempts to add a key-value pair to the collection if neither the key nor value already exists. - /// - /// The key to add. - /// The value to add. - /// True if the pair was added; false if either the key or value already exists. - public bool TryAdd(TKey key, TValue value) - { - if (ContainsKey(key) || _values.ContainsKey(value)) - return false; - - Add(key, value); - return true; - } - - /// - /// Removes a key-value pair from the collection based on the specified value. - /// - /// The value to remove. - /// True if the value was found and removed; otherwise, false. - public bool RemoveByValue(TValue value) - { - return _values.ContainsKey(value) && Remove(_values[value]); - } - - /// - /// Determines whether the collection contains a specific value. - /// - /// The value to check. - /// True if the value exists in the collection; otherwise, false. - public bool ContainsValue(TValue value) - { - return _values.ContainsKey(value); - } + #region Private Fields + + private readonly Dictionary _values; + + #endregion + + /// + /// Initializes a new instance of the class with default comparers. + /// + public PairSet() + { + _values = []; + } + + /// + /// Initializes a new instance of the class with a specified key comparer. + /// + /// The equality comparer for keys, or null to use the default comparer. + public PairSet(IEqualityComparer comparer) + : base(comparer) + { + _values = []; + } + + /// + /// Initializes a new instance of the class with specified key and value comparers. + /// + /// The equality comparer for keys, or null to use the default comparer. + /// The equality comparer for values, or null to use the default comparer. + public PairSet(IEqualityComparer keyComparer, IEqualityComparer valueComparer) + : base(keyComparer) + { + _values = new Dictionary(valueComparer); + } + + #region Item + + /// + /// Gets the key associated with the specified value. + /// + /// The value to look up. + /// The key associated with the specified value. + /// Thrown when the value is not found in the collection. + public TKey this[TValue value] => _values[value]; + + #endregion + + #region GetKey + + /// + /// Retrieves the key associated with the specified value. + /// + /// The value to look up. + /// The key associated with the specified value. + /// Thrown when the value is not found in the collection. + public TKey GetKey(TValue value) + { + return this[value]; + } + + #endregion + + #region GetValue + + /// + /// Retrieves the value associated with the specified key. + /// + /// The key to look up. + /// The value associated with the specified key. + /// Thrown when the key is not found in the collection. + public TValue GetValue(TKey key) + { + return base[key]; + } + + #endregion + + /// + /// Sets a new key for an existing value, replacing any previous association. + /// + /// The value to associate with the new key. + /// The new key to associate with the value. + public void SetKey(TValue value, TKey key) + { + RemoveByValue(value); + Add(key, value); + } + + #region SetValue + + /// + /// Sets a new value for an existing key, updating the internal value-to-key mapping. + /// + /// The key whose value is to be updated. + /// The new value to associate with the key. + public void SetValue(TKey key, TValue value) + { + base[key] = value; + } + + #endregion + + /// + /// Determines whether a key-value pair can be added to the collection. + /// + /// The key to check. + /// The value to check. + /// True if the value is not already associated with a key; otherwise, false. + protected override bool CanAdd(TKey key, TValue value) + { + return !_values.ContainsKey(value); + } + + #region KeyedCollection Members + + /// + /// Called before adding a key-value pair to the collection, updating the value-to-key mapping. + /// + /// The key being added. + /// The value being added. + protected override void OnAdding(TKey key, TValue value) + { + _values.Add(value, key); + } + + /// + /// Called before setting a new value for an existing key, updating the value-to-key mapping. + /// + /// The key being updated. + /// The new value to set. + protected override void OnSetting(TKey key, TValue value) + { + _values[value] = key; + } + + /// + /// Called before clearing the collection, clearing the value-to-key mapping. + /// + protected override void OnClearing() + { + _values.Clear(); + } + + /// + /// Called before removing a key-value pair, updating the value-to-key mapping. + /// + /// The key being removed. + /// The value being removed. + protected override void OnRemoving(TKey key, TValue value) + { + _values.Remove(value); + } + + #endregion + + /// + /// Attempts to retrieve the key associated with a specified value. + /// + /// The value to look up. + /// When this method returns, contains the key if found; otherwise, the default value of . + /// True if the value was found; otherwise, false. + public bool TryGetKey(TValue value, out TKey key) + { + return _values.TryGetValue(value, out key); + } + + /// + /// Attempts to add a key-value pair to the collection if neither the key nor value already exists. + /// + /// The key to add. + /// The value to add. + /// True if the pair was added; false if either the key or value already exists. + public bool TryAdd(TKey key, TValue value) + { + if (ContainsKey(key) || _values.ContainsKey(value)) + return false; + + Add(key, value); + return true; + } + + /// + /// Removes a key-value pair from the collection based on the specified value. + /// + /// The value to remove. + /// True if the value was found and removed; otherwise, false. + public bool RemoveByValue(TValue value) + { + return _values.ContainsKey(value) && Remove(_values[value]); + } + + /// + /// Determines whether the collection contains a specific value. + /// + /// The value to check. + /// True if the value exists in the collection; otherwise, false. + public bool ContainsValue(TValue value) + { + return _values.ContainsKey(value); } } \ No newline at end of file diff --git a/Collections/QueueEx.cs b/Collections/QueueEx.cs index 30feb5dc..0beb43dd 100644 --- a/Collections/QueueEx.cs +++ b/Collections/QueueEx.cs @@ -1,28 +1,27 @@ -namespace Ecng.Collections +namespace Ecng.Collections; + +using System; +using System.Collections.Generic; + +/// +/// Extends the class to explicitly implement the interface. +/// +/// The type of elements in the queue. +public class QueueEx : Queue, ICollection { - using System; - using System.Collections.Generic; + #region Implementation of ICollection - /// - /// Extends the class to explicitly implement the interface. - /// - /// The type of elements in the queue. - public class QueueEx : Queue, ICollection + void ICollection.Add(T item) { - #region Implementation of ICollection - - void ICollection.Add(T item) - { - Enqueue(item); - } + Enqueue(item); + } - bool ICollection.Remove(T item) - { - throw new NotSupportedException(); - } + bool ICollection.Remove(T item) + { + throw new NotSupportedException(); + } - bool ICollection.IsReadOnly => false; + bool ICollection.IsReadOnly => false; - #endregion - } + #endregion } \ No newline at end of file diff --git a/Collections/SimpleEnumerable.cs b/Collections/SimpleEnumerable.cs index 63497924..add54fb1 100644 --- a/Collections/SimpleEnumerable.cs +++ b/Collections/SimpleEnumerable.cs @@ -1,33 +1,32 @@ -namespace Ecng.Collections +namespace Ecng.Collections; + +using System; +using System.Collections; +using System.Collections.Generic; + +/// +/// Represents a simple implementation of an enumerable that uses a factory function to create its enumerator. +/// +/// The type of elements in the enumerable. +public class SimpleEnumerable(Func> createEnumerator) : IEnumerable { - using System; - using System.Collections; - using System.Collections.Generic; + private readonly Func> _createEnumerator = createEnumerator ?? throw new ArgumentNullException(nameof(createEnumerator)); /// - /// Represents a simple implementation of an enumerable that uses a factory function to create its enumerator. + /// Returns an enumerator that iterates through the collection. /// - /// The type of elements in the enumerable. - public class SimpleEnumerable(Func> createEnumerator) : IEnumerable + /// An for the collection. + public IEnumerator GetEnumerator() { - private readonly Func> _createEnumerator = createEnumerator ?? throw new ArgumentNullException(nameof(createEnumerator)); - - /// - /// Returns an enumerator that iterates through the collection. - /// - /// An for the collection. - public IEnumerator GetEnumerator() - { - return _createEnumerator(); - } + return _createEnumerator(); + } - /// - /// Returns an enumerator that iterates through the collection (non-generic version). - /// - /// An for the collection. - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + /// + /// Returns an enumerator that iterates through the collection (non-generic version). + /// + /// An for the collection. + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); } } \ No newline at end of file diff --git a/Collections/SimpleEnumerator.cs b/Collections/SimpleEnumerator.cs index a02fddd7..8e4fcee5 100644 --- a/Collections/SimpleEnumerator.cs +++ b/Collections/SimpleEnumerator.cs @@ -1,38 +1,37 @@ -namespace Ecng.Collections -{ - using System.Collections; - using System.Collections.Generic; +namespace Ecng.Collections; + +using System.Collections; +using System.Collections.Generic; - using Ecng.Common; +using Ecng.Common; +/// +/// Provides an abstract base class for implementing a simple enumerator with disposable functionality. +/// +/// The type of elements being enumerated. +public abstract class SimpleEnumerator : Disposable, IEnumerator +{ /// - /// Provides an abstract base class for implementing a simple enumerator with disposable functionality. + /// Advances the enumerator to the next element of the collection. /// - /// The type of elements being enumerated. - public abstract class SimpleEnumerator : Disposable, IEnumerator - { - /// - /// Advances the enumerator to the next element of the collection. - /// - /// True if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection. - public abstract bool MoveNext(); + /// True if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection. + public abstract bool MoveNext(); - /// - /// Sets the enumerator to its initial position, which is before the first element in the collection. - /// - /// This implementation does nothing by default and can be overridden by derived classes. - public virtual void Reset() - { - } + /// + /// Sets the enumerator to its initial position, which is before the first element in the collection. + /// + /// This implementation does nothing by default and can be overridden by derived classes. + public virtual void Reset() + { + } - /// - /// Gets the current element in the collection. - /// - public T Current { get; protected set; } + /// + /// Gets the current element in the collection. + /// + public T Current { get; protected set; } - /// - /// Gets the current element in the collection as an object (non-generic version). - /// - object IEnumerator.Current => Current; - } + /// + /// Gets the current element in the collection as an object (non-generic version). + /// + object IEnumerator.Current => Current; } \ No newline at end of file diff --git a/Collections/StackEx.cs b/Collections/StackEx.cs index d8262339..3d6bf3de 100644 --- a/Collections/StackEx.cs +++ b/Collections/StackEx.cs @@ -1,39 +1,38 @@ -namespace Ecng.Collections -{ - using System; - using System.Collections.Generic; +namespace Ecng.Collections; + +using System; +using System.Collections.Generic; +/// +/// Extends the class to explicitly implement the interface. +/// +/// The type of elements in the stack. +public class StackEx : Stack, ICollection +{ /// - /// Extends the class to explicitly implement the interface. + /// Adds an item to the top of the stack. /// - /// The type of elements in the stack. - public class StackEx : Stack, ICollection + /// The item to add. + /// This method delegates to . + void ICollection.Add(T item) { - /// - /// Adds an item to the top of the stack. - /// - /// The item to add. - /// This method delegates to . - void ICollection.Add(T item) - { - Push(item); - } - - /// - /// Attempts to remove a specific item from the stack. This operation is not supported. - /// - /// The item to remove. - /// Always throws . - /// Thrown because removing a specific item is not supported by a stack. - bool ICollection.Remove(T item) - { - throw new NotSupportedException(); - } + Push(item); + } - /// - /// Gets a value indicating whether the stack is read-only. - /// - /// Always returns false, as stacks are inherently mutable. - bool ICollection.IsReadOnly => false; + /// + /// Attempts to remove a specific item from the stack. This operation is not supported. + /// + /// The item to remove. + /// Always throws . + /// Thrown because removing a specific item is not supported by a stack. + bool ICollection.Remove(T item) + { + throw new NotSupportedException(); } + + /// + /// Gets a value indicating whether the stack is read-only. + /// + /// Always returns false, as stacks are inherently mutable. + bool ICollection.IsReadOnly => false; } \ No newline at end of file diff --git a/Collections/SynchronizedCollection.cs b/Collections/SynchronizedCollection.cs index da0a39c7..833bbf74 100644 --- a/Collections/SynchronizedCollection.cs +++ b/Collections/SynchronizedCollection.cs @@ -1,143 +1,142 @@ -namespace Ecng.Collections -{ - using System; - using System.Collections.Generic; +namespace Ecng.Collections; + +using System; +using System.Collections.Generic; - using Ecng.Common; +using Ecng.Common; +/// +/// Represents a thread-safe collection that provides synchronization for its operations. +/// +/// The type of elements in the collection. +/// The type of the inner collection. +[Serializable] +public abstract class SynchronizedCollection(TCollection innerCollection) : BaseCollection(innerCollection), ISynchronizedCollection + where TCollection : ICollection +{ /// - /// Represents a thread-safe collection that provides synchronization for its operations. + /// Gets the synchronization root object used to synchronize access to the collection. /// - /// The type of elements in the collection. - /// The type of the inner collection. - [Serializable] - public abstract class SynchronizedCollection(TCollection innerCollection) : BaseCollection(innerCollection), ISynchronizedCollection - where TCollection : ICollection - { - /// - /// Gets the synchronization root object used to synchronize access to the collection. - /// - public SyncObject SyncRoot { get; } = new SyncObject(); - - /// - /// Gets the number of elements contained in the collection. - /// - public override int Count - { - get - { - lock (SyncRoot) - return base.Count; - } - } - - /// - /// Gets or sets the element at the specified index. - /// - /// The zero-based index of the element to get or set. - /// The element at the specified index. - public override TItem this[int index] - { - get - { - lock (SyncRoot) - return base[index]; - } - set - { - lock (SyncRoot) - base[index] = value; - } - } + public SyncObject SyncRoot { get; } = new SyncObject(); - /// - /// Adds an item to the collection. - /// - /// The item to add to the collection. - public override void Add(TItem item) + /// + /// Gets the number of elements contained in the collection. + /// + public override int Count + { + get { lock (SyncRoot) - base.Add(item); + return base.Count; } + } - /// - /// Removes all items from the collection. - /// - public override void Clear() + /// + /// Gets or sets the element at the specified index. + /// + /// The zero-based index of the element to get or set. + /// The element at the specified index. + public override TItem this[int index] + { + get { lock (SyncRoot) - base.Clear(); + return base[index]; } - - /// - /// Removes the first occurrence of a specific item from the collection. - /// - /// The item to remove from the collection. - /// true if the item was successfully removed; otherwise, false. - public override bool Remove(TItem item) + set { lock (SyncRoot) - return base.Remove(item); + base[index] = value; } + } - /// - /// Removes the item at the specified index. - /// - /// The zero-based index of the item to remove. - public override void RemoveAt(int index) - { - lock (SyncRoot) - base.RemoveAt(index); - } + /// + /// Adds an item to the collection. + /// + /// The item to add to the collection. + public override void Add(TItem item) + { + lock (SyncRoot) + base.Add(item); + } - /// - /// Inserts an item into the collection at the specified index. - /// - /// The zero-based index at which the item should be inserted. - /// The item to insert into the collection. - public override void Insert(int index, TItem item) - { - lock (SyncRoot) - base.Insert(index, item); - } + /// + /// Removes all items from the collection. + /// + public override void Clear() + { + lock (SyncRoot) + base.Clear(); + } - /// - /// Determines the index of a specific item in the collection. - /// - /// The item to locate in the collection. - /// The index of the item if found in the collection; otherwise, -1. - public override int IndexOf(TItem item) - { - lock (SyncRoot) - return OnIndexOf(item); - } + /// + /// Removes the first occurrence of a specific item from the collection. + /// + /// The item to remove from the collection. + /// true if the item was successfully removed; otherwise, false. + public override bool Remove(TItem item) + { + lock (SyncRoot) + return base.Remove(item); + } - /// - /// Determines whether the collection contains a specific item. - /// - /// The item to locate in the collection. - /// true if the item is found in the collection; otherwise, false. - public override bool Contains(TItem item) - { - lock (SyncRoot) - return base.Contains(item); - } + /// + /// Removes the item at the specified index. + /// + /// The zero-based index of the item to remove. + public override void RemoveAt(int index) + { + lock (SyncRoot) + base.RemoveAt(index); + } - /// - /// When overridden in a derived class, determines the index of a specific item in the collection. - /// - /// The item to locate in the collection. - /// The index of the item if found in the collection; otherwise, -1. - protected abstract int OnIndexOf(TItem item); + /// + /// Inserts an item into the collection at the specified index. + /// + /// The zero-based index at which the item should be inserted. + /// The item to insert into the collection. + public override void Insert(int index, TItem item) + { + lock (SyncRoot) + base.Insert(index, item); + } - /// - /// Returns an enumerator that iterates through the collection. - /// - /// An enumerator that can be used to iterate through the collection. - public override IEnumerator GetEnumerator() - { - lock (SyncRoot) - return InnerCollection.GetEnumerator(); - } + /// + /// Determines the index of a specific item in the collection. + /// + /// The item to locate in the collection. + /// The index of the item if found in the collection; otherwise, -1. + public override int IndexOf(TItem item) + { + lock (SyncRoot) + return OnIndexOf(item); + } + + /// + /// Determines whether the collection contains a specific item. + /// + /// The item to locate in the collection. + /// true if the item is found in the collection; otherwise, false. + public override bool Contains(TItem item) + { + lock (SyncRoot) + return base.Contains(item); + } + + /// + /// When overridden in a derived class, determines the index of a specific item in the collection. + /// + /// The item to locate in the collection. + /// The index of the item if found in the collection; otherwise, -1. + protected abstract int OnIndexOf(TItem item); + + /// + /// Returns an enumerator that iterates through the collection. + /// + /// An enumerator that can be used to iterate through the collection. + public override IEnumerator GetEnumerator() + { + lock (SyncRoot) + return InnerCollection.GetEnumerator(); } } \ No newline at end of file diff --git a/Collections/SynchronizedDictionary.cs b/Collections/SynchronizedDictionary.cs index ad014a61..2d0f14d4 100644 --- a/Collections/SynchronizedDictionary.cs +++ b/Collections/SynchronizedDictionary.cs @@ -1,243 +1,242 @@ -namespace Ecng.Collections +namespace Ecng.Collections; + +using System; +using System.Collections; +using System.Collections.Generic; + +using Ecng.Common; + +/// +/// Represents a thread-safe dictionary that provides synchronization for its operations. +/// +/// The type of keys in the dictionary. +/// The type of values in the dictionary. +[Serializable] +public class SynchronizedDictionary : ISynchronizedCollection>, IDictionary { - using System; - using System.Collections; - using System.Collections.Generic; + private readonly IDictionary _inner; - using Ecng.Common; + /// + /// Initializes a new instance of the class. + /// + public SynchronizedDictionary() + : this(new Dictionary()) + { + } /// - /// Represents a thread-safe dictionary that provides synchronization for its operations. + /// Initializes a new instance of the class with the specified initial capacity. /// - /// The type of keys in the dictionary. - /// The type of values in the dictionary. - [Serializable] - public class SynchronizedDictionary : ISynchronizedCollection>, IDictionary + /// The initial number of elements that the dictionary can contain. + public SynchronizedDictionary(int capacity) + : this(new Dictionary(capacity)) { - private readonly IDictionary _inner; + } - /// - /// Initializes a new instance of the class. - /// - public SynchronizedDictionary() - : this(new Dictionary()) - { - } + /// + /// Initializes a new instance of the class with the specified equality comparer. + /// + /// The equality comparer to use when comparing keys. + public SynchronizedDictionary(IEqualityComparer comparer) + : this(new Dictionary(comparer)) + { + } - /// - /// Initializes a new instance of the class with the specified initial capacity. - /// - /// The initial number of elements that the dictionary can contain. - public SynchronizedDictionary(int capacity) - : this(new Dictionary(capacity)) - { - } + /// + /// Initializes a new instance of the class with the specified initial capacity and equality comparer. + /// + /// The initial number of elements that the dictionary can contain. + /// The equality comparer to use when comparing keys. + public SynchronizedDictionary(int capacity, IEqualityComparer comparer) + : this(new Dictionary(capacity, comparer)) + { + } - /// - /// Initializes a new instance of the class with the specified equality comparer. - /// - /// The equality comparer to use when comparing keys. - public SynchronizedDictionary(IEqualityComparer comparer) - : this(new Dictionary(comparer)) - { - } + /// + /// Gets the synchronization root object used to synchronize access to the dictionary. + /// + public SyncObject SyncRoot { get; } = new SyncObject(); - /// - /// Initializes a new instance of the class with the specified initial capacity and equality comparer. - /// - /// The initial number of elements that the dictionary can contain. - /// The equality comparer to use when comparing keys. - public SynchronizedDictionary(int capacity, IEqualityComparer comparer) - : this(new Dictionary(capacity, comparer)) - { - } + /// + /// Initializes a new instance of the class with the specified inner dictionary. + /// + /// The inner dictionary to wrap. + /// Thrown if is null. + protected SynchronizedDictionary(IDictionary inner) + { + _inner = inner ?? throw new ArgumentNullException(nameof(inner)); + } - /// - /// Gets the synchronization root object used to synchronize access to the dictionary. - /// - public SyncObject SyncRoot { get; } = new SyncObject(); - - /// - /// Initializes a new instance of the class with the specified inner dictionary. - /// - /// The inner dictionary to wrap. - /// Thrown if is null. - protected SynchronizedDictionary(IDictionary inner) - { - _inner = inner ?? throw new ArgumentNullException(nameof(inner)); - } + /// + /// Returns an enumerator that iterates through the dictionary. + /// + /// An enumerator that can be used to iterate through the dictionary. + public IEnumerator> GetEnumerator() + { + lock (SyncRoot) + return _inner.GetEnumerator(); + } - /// - /// Returns an enumerator that iterates through the dictionary. - /// - /// An enumerator that can be used to iterate through the dictionary. - public IEnumerator> GetEnumerator() - { - lock (SyncRoot) - return _inner.GetEnumerator(); - } + /// + /// Returns an enumerator that iterates through the dictionary. + /// + /// An enumerator that can be used to iterate through the dictionary. + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } - /// - /// Returns an enumerator that iterates through the dictionary. - /// - /// An enumerator that can be used to iterate through the dictionary. - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + /// + /// Adds a key-value pair to the dictionary. + /// + /// The key-value pair to add. + public void Add(KeyValuePair item) + { + Add(item.Key, item.Value); + } - /// - /// Adds a key-value pair to the dictionary. - /// - /// The key-value pair to add. - public void Add(KeyValuePair item) - { - Add(item.Key, item.Value); - } + /// + /// Removes all keys and values from the dictionary. + /// + public virtual void Clear() + { + lock (SyncRoot) + _inner.Clear(); + } - /// - /// Removes all keys and values from the dictionary. - /// - public virtual void Clear() - { - lock (SyncRoot) - _inner.Clear(); - } + /// + /// Determines whether the dictionary contains a specific key-value pair. + /// + /// The key-value pair to locate in the dictionary. + /// true if the dictionary contains the key-value pair; otherwise, false. + public bool Contains(KeyValuePair item) + { + return ContainsKey(item.Key); + } - /// - /// Determines whether the dictionary contains a specific key-value pair. - /// - /// The key-value pair to locate in the dictionary. - /// true if the dictionary contains the key-value pair; otherwise, false. - public bool Contains(KeyValuePair item) - { - return ContainsKey(item.Key); - } + /// + /// Copies the elements of the dictionary to an array, starting at a particular array index. + /// + /// The one-dimensional array that is the destination of the elements copied from the dictionary. + /// The zero-based index in at which copying begins. + public virtual void CopyTo(KeyValuePair[] array, int arrayIndex) + { + lock (SyncRoot) + _inner.CopyTo(array, arrayIndex); + } + + /// + /// Removes the first occurrence of a specific key-value pair from the dictionary. + /// + /// The key-value pair to remove. + /// true if the key-value pair was successfully removed; otherwise, false. + public bool Remove(KeyValuePair item) + { + return Remove(item.Key); + } - /// - /// Copies the elements of the dictionary to an array, starting at a particular array index. - /// - /// The one-dimensional array that is the destination of the elements copied from the dictionary. - /// The zero-based index in at which copying begins. - public virtual void CopyTo(KeyValuePair[] array, int arrayIndex) + /// + /// Gets the number of key-value pairs contained in the dictionary. + /// + public int Count + { + get { lock (SyncRoot) - _inner.CopyTo(array, arrayIndex); + return _inner.Count; } + } - /// - /// Removes the first occurrence of a specific key-value pair from the dictionary. - /// - /// The key-value pair to remove. - /// true if the key-value pair was successfully removed; otherwise, false. - public bool Remove(KeyValuePair item) - { - return Remove(item.Key); - } + /// + /// Gets a value indicating whether the dictionary is read-only. + /// + public bool IsReadOnly => false; - /// - /// Gets the number of key-value pairs contained in the dictionary. - /// - public int Count - { - get - { - lock (SyncRoot) - return _inner.Count; - } - } + /// + /// Determines whether the dictionary contains the specified key. + /// + /// The key to locate in the dictionary. + /// true if the dictionary contains an element with the specified key; otherwise, false. + public virtual bool ContainsKey(TKey key) + { + lock (SyncRoot) + return _inner.ContainsKey(key); + } - /// - /// Gets a value indicating whether the dictionary is read-only. - /// - public bool IsReadOnly => false; - - /// - /// Determines whether the dictionary contains the specified key. - /// - /// The key to locate in the dictionary. - /// true if the dictionary contains an element with the specified key; otherwise, false. - public virtual bool ContainsKey(TKey key) - { - lock (SyncRoot) - return _inner.ContainsKey(key); - } + /// + /// Adds the specified key and value to the dictionary. + /// + /// The key of the element to add. + /// The value of the element to add. + public virtual void Add(TKey key, TValue value) + { + lock (SyncRoot) + _inner.Add(key, value); + } - /// - /// Adds the specified key and value to the dictionary. - /// - /// The key of the element to add. - /// The value of the element to add. - public virtual void Add(TKey key, TValue value) - { - lock (SyncRoot) - _inner.Add(key, value); - } + /// + /// Removes the value with the specified key from the dictionary. + /// + /// The key of the element to remove. + /// true if the element is successfully found and removed; otherwise, false. + public virtual bool Remove(TKey key) + { + lock (SyncRoot) + return _inner.Remove(key); + } - /// - /// Removes the value with the specified key from the dictionary. - /// - /// The key of the element to remove. - /// true if the element is successfully found and removed; otherwise, false. - public virtual bool Remove(TKey key) - { - lock (SyncRoot) - return _inner.Remove(key); - } + /// + /// Gets the value associated with the specified key. + /// + /// The key of the value to get. + /// When this method returns, contains the value associated with the specified key, if the key is found; otherwise, the default value for the type of the parameter. + /// true if the dictionary contains an element with the specified key; otherwise, false. + public virtual bool TryGetValue(TKey key, out TValue value) + { + lock (SyncRoot) + return _inner.TryGetValue(key, out value); + } - /// - /// Gets the value associated with the specified key. - /// - /// The key of the value to get. - /// When this method returns, contains the value associated with the specified key, if the key is found; otherwise, the default value for the type of the parameter. - /// true if the dictionary contains an element with the specified key; otherwise, false. - public virtual bool TryGetValue(TKey key, out TValue value) + /// + /// Gets or sets the value associated with the specified key. + /// + /// The key of the value to get or set. + /// The value associated with the specified key. + public virtual TValue this[TKey key] + { + get { lock (SyncRoot) - return _inner.TryGetValue(key, out value); + return _inner[key]; } - - /// - /// Gets or sets the value associated with the specified key. - /// - /// The key of the value to get or set. - /// The value associated with the specified key. - public virtual TValue this[TKey key] + set { - get - { - lock (SyncRoot) - return _inner[key]; - } - set - { - lock (SyncRoot) - _inner[key] = value; - } + lock (SyncRoot) + _inner[key] = value; } + } - /// - /// Gets a collection containing the keys in the dictionary. - /// - public ICollection Keys + /// + /// Gets a collection containing the keys in the dictionary. + /// + public ICollection Keys + { + get { - get - { - lock (SyncRoot) - return _inner.Keys; - } + lock (SyncRoot) + return _inner.Keys; } + } - /// - /// Gets a collection containing the values in the dictionary. - /// - public ICollection Values + /// + /// Gets a collection containing the values in the dictionary. + /// + public ICollection Values + { + get { - get - { - lock (SyncRoot) - return _inner.Values; - } + lock (SyncRoot) + return _inner.Values; } } } \ No newline at end of file diff --git a/Collections/SynchronizedKeyedCollection.cs b/Collections/SynchronizedKeyedCollection.cs index 2a1169a0..0bec300e 100644 --- a/Collections/SynchronizedKeyedCollection.cs +++ b/Collections/SynchronizedKeyedCollection.cs @@ -1,77 +1,76 @@ -namespace Ecng.Collections -{ - using System; - using System.Collections.Generic; +namespace Ecng.Collections; + +using System; +using System.Collections.Generic; - using Ecng.Common; +using Ecng.Common; +/// +/// Represents a thread-safe keyed collection that provides synchronization for its operations. +/// +/// The type of keys in the collection. +/// The type of values in the collection. +[Serializable] +public abstract class SynchronizedKeyedCollection : KeyedCollection, ISynchronizedCollection> +{ /// - /// Represents a thread-safe keyed collection that provides synchronization for its operations. + /// Initializes a new instance of the class. /// - /// The type of keys in the collection. - /// The type of values in the collection. - [Serializable] - public abstract class SynchronizedKeyedCollection : KeyedCollection, ISynchronizedCollection> + protected SynchronizedKeyedCollection() + : base(new SynchronizedDictionary()) { - /// - /// Initializes a new instance of the class. - /// - protected SynchronizedKeyedCollection() - : base(new SynchronizedDictionary()) - { - } - - /// - /// Initializes a new instance of the class with the specified equality comparer. - /// - /// The equality comparer to use when comparing keys. - protected SynchronizedKeyedCollection(IEqualityComparer comparer) - : base(new SynchronizedDictionary(comparer)) - { - } + } - /// - /// Gets the synchronized dictionary used internally by the collection. - /// - private SynchronizedDictionary SyncDict => (SynchronizedDictionary)InnerDictionary; + /// + /// Initializes a new instance of the class with the specified equality comparer. + /// + /// The equality comparer to use when comparing keys. + protected SynchronizedKeyedCollection(IEqualityComparer comparer) + : base(new SynchronizedDictionary(comparer)) + { + } - /// - /// Gets the synchronization root object used to synchronize access to the collection. - /// - public SyncObject SyncRoot => SyncDict.SyncRoot; + /// + /// Gets the synchronized dictionary used internally by the collection. + /// + private SynchronizedDictionary SyncDict => (SynchronizedDictionary)InnerDictionary; - /// - /// Gets or sets the value associated with the specified key. - /// - /// The key of the value to get or set. - /// The value associated with the specified key. - public override TValue this[TKey key] - { - set - { - lock (SyncRoot) - base[key] = value; - } - } + /// + /// Gets the synchronization root object used to synchronize access to the collection. + /// + public SyncObject SyncRoot => SyncDict.SyncRoot; - /// - /// Removes all keys and values from the collection. - /// - public override void Clear() + /// + /// Gets or sets the value associated with the specified key. + /// + /// The key of the value to get or set. + /// The value associated with the specified key. + public override TValue this[TKey key] + { + set { lock (SyncRoot) - base.Clear(); + base[key] = value; } + } - /// - /// Removes the value with the specified key from the collection. - /// - /// The key of the element to remove. - /// true if the element is successfully found and removed; otherwise, false. - public override bool Remove(TKey key) - { - lock (SyncRoot) - return base.Remove(key); - } + /// + /// Removes all keys and values from the collection. + /// + public override void Clear() + { + lock (SyncRoot) + base.Clear(); + } + + /// + /// Removes the value with the specified key from the collection. + /// + /// The key of the element to remove. + /// true if the element is successfully found and removed; otherwise, false. + public override bool Remove(TKey key) + { + lock (SyncRoot) + return base.Remove(key); } } \ No newline at end of file diff --git a/Collections/SynchronizedLinkedList.cs b/Collections/SynchronizedLinkedList.cs index 58e16363..057d28cb 100644 --- a/Collections/SynchronizedLinkedList.cs +++ b/Collections/SynchronizedLinkedList.cs @@ -1,247 +1,246 @@ -namespace Ecng.Collections -{ - using System.Collections; - using System.Collections.Generic; - - using Ecng.Common; +namespace Ecng.Collections; - /// - /// Represents a thread-safe linked list that provides synchronization for its operations. - /// - /// The type of elements in the linked list. - public class SynchronizedLinkedList : ISynchronizedCollection - { - private readonly LinkedList _inner = new(); +using System.Collections; +using System.Collections.Generic; - private readonly SyncObject _syncRoot = new(); +using Ecng.Common; - /// - /// Gets the synchronization root object used to synchronize access to the linked list. - /// - public SyncObject SyncRoot => _syncRoot; +/// +/// Represents a thread-safe linked list that provides synchronization for its operations. +/// +/// The type of elements in the linked list. +public class SynchronizedLinkedList : ISynchronizedCollection +{ + private readonly LinkedList _inner = new(); - /// - /// Gets the first node of the linked list. - /// - public virtual LinkedListNode First - { - get - { - lock (SyncRoot) - return _inner.First; - } - } + private readonly SyncObject _syncRoot = new(); - /// - /// Gets the last node of the linked list. - /// - public virtual LinkedListNode Last - { - get - { - lock (SyncRoot) - return _inner.Last; - } - } + /// + /// Gets the synchronization root object used to synchronize access to the linked list. + /// + public SyncObject SyncRoot => _syncRoot; - /// - /// Adds a new node containing the specified value before the specified existing node. - /// - /// The node before which the new node should be added. - /// The value to add to the linked list. - public virtual void AddBefore(LinkedListNode node, T value) + /// + /// Gets the first node of the linked list. + /// + public virtual LinkedListNode First + { + get { lock (SyncRoot) - _inner.AddBefore(node, value); + return _inner.First; } + } - /// - /// Adds the specified new node before the specified existing node. - /// - /// The node before which the new node should be added. - /// The new node to add. - public virtual void AddBefore(LinkedListNode node, LinkedListNode newNode) + /// + /// Gets the last node of the linked list. + /// + public virtual LinkedListNode Last + { + get { lock (SyncRoot) - _inner.AddBefore(node, newNode); + return _inner.Last; } + } - /// - /// Adds a new node containing the specified value at the start of the linked list. - /// - /// The value to add to the linked list. - public virtual void AddFirst(T value) - { - lock (SyncRoot) - _inner.AddFirst(value); - } + /// + /// Adds a new node containing the specified value before the specified existing node. + /// + /// The node before which the new node should be added. + /// The value to add to the linked list. + public virtual void AddBefore(LinkedListNode node, T value) + { + lock (SyncRoot) + _inner.AddBefore(node, value); + } - /// - /// Adds the specified new node at the start of the linked list. - /// - /// The new node to add. - public virtual void AddFirst(LinkedListNode node) - { - lock (SyncRoot) - _inner.AddFirst(node); - } + /// + /// Adds the specified new node before the specified existing node. + /// + /// The node before which the new node should be added. + /// The new node to add. + public virtual void AddBefore(LinkedListNode node, LinkedListNode newNode) + { + lock (SyncRoot) + _inner.AddBefore(node, newNode); + } - /// - /// Adds a new node containing the specified value at the end of the linked list. - /// - /// The value to add to the linked list. - public virtual void AddLast(T value) - { - lock (SyncRoot) - _inner.AddLast(value); - } + /// + /// Adds a new node containing the specified value at the start of the linked list. + /// + /// The value to add to the linked list. + public virtual void AddFirst(T value) + { + lock (SyncRoot) + _inner.AddFirst(value); + } - /// - /// Adds the specified new node at the end of the linked list. - /// - /// The new node to add. - public virtual void AddLast(LinkedListNode node) - { - lock (SyncRoot) - _inner.AddLast(node); - } + /// + /// Adds the specified new node at the start of the linked list. + /// + /// The new node to add. + public virtual void AddFirst(LinkedListNode node) + { + lock (SyncRoot) + _inner.AddFirst(node); + } - /// - /// Removes the specified node from the linked list. - /// - /// The node to remove. - public virtual void Remove(LinkedListNode node) - { - lock (SyncRoot) - _inner.Remove(node); - } + /// + /// Adds a new node containing the specified value at the end of the linked list. + /// + /// The value to add to the linked list. + public virtual void AddLast(T value) + { + lock (SyncRoot) + _inner.AddLast(value); + } - /// - /// Removes the node at the start of the linked list. - /// - public virtual void RemoveFirst() - { - lock (SyncRoot) - _inner.RemoveFirst(); - } + /// + /// Adds the specified new node at the end of the linked list. + /// + /// The new node to add. + public virtual void AddLast(LinkedListNode node) + { + lock (SyncRoot) + _inner.AddLast(node); + } - /// - /// Removes the node at the end of the linked list. - /// - public virtual void RemoveLast() - { - lock (SyncRoot) - _inner.RemoveLast(); - } + /// + /// Removes the specified node from the linked list. + /// + /// The node to remove. + public virtual void Remove(LinkedListNode node) + { + lock (SyncRoot) + _inner.Remove(node); + } - /// - /// Finds the first node that contains the specified value. - /// - /// The value to locate in the linked list. - /// The first node that contains the specified value, if found; otherwise, null. - public virtual LinkedListNode Find(T value) - { - lock (SyncRoot) - return _inner.Find(value); - } + /// + /// Removes the node at the start of the linked list. + /// + public virtual void RemoveFirst() + { + lock (SyncRoot) + _inner.RemoveFirst(); + } - /// - /// Finds the last node that contains the specified value. - /// - /// The value to locate in the linked list. - /// The last node that contains the specified value, if found; otherwise, null. - public virtual LinkedListNode FindLast(T value) - { - lock (SyncRoot) - return _inner.FindLast(value); - } + /// + /// Removes the node at the end of the linked list. + /// + public virtual void RemoveLast() + { + lock (SyncRoot) + _inner.RemoveLast(); + } - /// - /// Returns an enumerator that iterates through the linked list. - /// - /// An enumerator that can be used to iterate through the linked list. - public IEnumerator GetEnumerator() - { - lock (SyncRoot) - return _inner.GetEnumerator(); - } + /// + /// Finds the first node that contains the specified value. + /// + /// The value to locate in the linked list. + /// The first node that contains the specified value, if found; otherwise, null. + public virtual LinkedListNode Find(T value) + { + lock (SyncRoot) + return _inner.Find(value); + } - /// - /// Returns an enumerator that iterates through the linked list. - /// - /// An enumerator that can be used to iterate through the linked list. - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + /// + /// Finds the last node that contains the specified value. + /// + /// The value to locate in the linked list. + /// The last node that contains the specified value, if found; otherwise, null. + public virtual LinkedListNode FindLast(T value) + { + lock (SyncRoot) + return _inner.FindLast(value); + } - /// - /// Adds an item to the linked list. - /// - /// The item to add to the linked list. - void ICollection.Add(T item) - { - lock (SyncRoot) - ((ICollection)_inner).Add(item); - } + /// + /// Returns an enumerator that iterates through the linked list. + /// + /// An enumerator that can be used to iterate through the linked list. + public IEnumerator GetEnumerator() + { + lock (SyncRoot) + return _inner.GetEnumerator(); + } - /// - /// Removes all nodes from the linked list. - /// - public void Clear() - { - lock (SyncRoot) - _inner.Clear(); - } + /// + /// Returns an enumerator that iterates through the linked list. + /// + /// An enumerator that can be used to iterate through the linked list. + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } - /// - /// Determines whether the linked list contains the specified value. - /// - /// The value to locate in the linked list. - /// true if the value is found in the linked list; otherwise, false. - public bool Contains(T item) - { - lock (SyncRoot) - return _inner.Contains(item); - } + /// + /// Adds an item to the linked list. + /// + /// The item to add to the linked list. + void ICollection.Add(T item) + { + lock (SyncRoot) + ((ICollection)_inner).Add(item); + } - /// - /// Copies the elements of the linked list to an array, starting at a particular array index. - /// - /// The one-dimensional array that is the destination of the elements copied from the linked list. - /// The zero-based index in at which copying begins. - public void CopyTo(T[] array, int arrayIndex) - { - lock (SyncRoot) - _inner.CopyTo(array, arrayIndex); - } + /// + /// Removes all nodes from the linked list. + /// + public void Clear() + { + lock (SyncRoot) + _inner.Clear(); + } - /// - /// Removes the first occurrence of the specified value from the linked list. - /// - /// The value to remove from the linked list. - /// true if the value was successfully removed; otherwise, false. - public bool Remove(T item) - { - lock (SyncRoot) - return _inner.Remove(item); - } + /// + /// Determines whether the linked list contains the specified value. + /// + /// The value to locate in the linked list. + /// true if the value is found in the linked list; otherwise, false. + public bool Contains(T item) + { + lock (SyncRoot) + return _inner.Contains(item); + } - /// - /// Gets the number of nodes contained in the linked list. - /// - public int Count + /// + /// Copies the elements of the linked list to an array, starting at a particular array index. + /// + /// The one-dimensional array that is the destination of the elements copied from the linked list. + /// The zero-based index in at which copying begins. + public void CopyTo(T[] array, int arrayIndex) + { + lock (SyncRoot) + _inner.CopyTo(array, arrayIndex); + } + + /// + /// Removes the first occurrence of the specified value from the linked list. + /// + /// The value to remove from the linked list. + /// true if the value was successfully removed; otherwise, false. + public bool Remove(T item) + { + lock (SyncRoot) + return _inner.Remove(item); + } + + /// + /// Gets the number of nodes contained in the linked list. + /// + public int Count + { + get { - get - { - lock (SyncRoot) - return _inner.Count; - } + lock (SyncRoot) + return _inner.Count; } - - /// - /// Gets a value indicating whether the linked list is read-only. - /// - bool ICollection.IsReadOnly => false; } + + /// + /// Gets a value indicating whether the linked list is read-only. + /// + bool ICollection.IsReadOnly => false; } \ No newline at end of file diff --git a/Collections/SynchronizedList.cs b/Collections/SynchronizedList.cs index 05595b83..d47d96c2 100644 --- a/Collections/SynchronizedList.cs +++ b/Collections/SynchronizedList.cs @@ -1,172 +1,171 @@ -namespace Ecng.Collections +namespace Ecng.Collections; + +using System; +using System.Collections.Generic; +using System.Linq; + +using Ecng.Common; + +/// +/// Represents a thread-safe list that supports range operations and notification events. +/// +/// The type of elements in the list. +[Serializable] +public class SynchronizedList(int capacity) : SynchronizedCollection>(new List(capacity)), INotifyListEx { - using System; - using System.Collections.Generic; - using System.Linq; + /// + /// Initializes a new instance of the class with default capacity. + /// + public SynchronizedList() + : this(0) + { + } - using Ecng.Common; + /// + /// Retrieves an item at the specified index from the inner list. + /// + /// The zero-based index of the item to retrieve. + /// The item at the specified index. + protected override T OnGetItem(int index) + { + return InnerCollection[index]; + } /// - /// Represents a thread-safe list that supports range operations and notification events. + /// Inserts an item into the inner list at the specified index. /// - /// The type of elements in the list. - [Serializable] - public class SynchronizedList(int capacity) : SynchronizedCollection>(new List(capacity)), INotifyListEx + /// The zero-based index at which to insert the item. + /// The item to insert. + protected override void OnInsert(int index, T item) { - /// - /// Initializes a new instance of the class with default capacity. - /// - public SynchronizedList() - : this(0) - { - } + InnerCollection.Insert(index, item); + } - /// - /// Retrieves an item at the specified index from the inner list. - /// - /// The zero-based index of the item to retrieve. - /// The item at the specified index. - protected override T OnGetItem(int index) - { - return InnerCollection[index]; - } + /// + /// Removes an item from the inner list at the specified index. + /// + /// The zero-based index of the item to remove. + protected override void OnRemoveAt(int index) + { + InnerCollection.RemoveAt(index); + } - /// - /// Inserts an item into the inner list at the specified index. - /// - /// The zero-based index at which to insert the item. - /// The item to insert. - protected override void OnInsert(int index, T item) - { - InnerCollection.Insert(index, item); - } + /// + /// Returns the index of the specified item in the inner list. + /// + /// The item to locate. + /// The zero-based index of the item, or -1 if not found. + protected override int OnIndexOf(T item) + { + return InnerCollection.IndexOf(item); + } - /// - /// Removes an item from the inner list at the specified index. - /// - /// The zero-based index of the item to remove. - protected override void OnRemoveAt(int index) - { - InnerCollection.RemoveAt(index); - } + /// + /// Occurs when a range of items is added to the list. + /// + public event Action> AddedRange; - /// - /// Returns the index of the specified item in the inner list. - /// - /// The item to locate. - /// The zero-based index of the item, or -1 if not found. - protected override int OnIndexOf(T item) - { - return InnerCollection.IndexOf(item); - } + /// + /// Occurs when a range of items is removed from the list. + /// + public event Action> RemovedRange; - /// - /// Occurs when a range of items is added to the list. - /// - public event Action> AddedRange; - - /// - /// Occurs when a range of items is removed from the list. - /// - public event Action> RemovedRange; - - /// - /// Called after an item is added to the list, raising the event. - /// - /// The item that was added. - protected override void OnAdded(T item) - { - base.OnAdded(item); + /// + /// Called after an item is added to the list, raising the event. + /// + /// The item that was added. + protected override void OnAdded(T item) + { + base.OnAdded(item); - var evt = AddedRange; - evt?.Invoke([item]); - } + var evt = AddedRange; + evt?.Invoke([item]); + } - /// - /// Called after an item is removed from the list, raising the event. - /// - /// The item that was removed. - protected override void OnRemoved(T item) - { - base.OnRemoved(item); + /// + /// Called after an item is removed from the list, raising the event. + /// + /// The item that was removed. + protected override void OnRemoved(T item) + { + base.OnRemoved(item); - var evt = RemovedRange; - evt?.Invoke([item]); - } + var evt = RemovedRange; + evt?.Invoke([item]); + } - /// - /// Adds a range of items to the list in a thread-safe manner. - /// - /// The items to add. - /// Thrown when an item is null and is true. - public void AddRange(IEnumerable items) + /// + /// Adds a range of items to the list in a thread-safe manner. + /// + /// The items to add. + /// Thrown when an item is null and is true. + public void AddRange(IEnumerable items) + { + lock (SyncRoot) { - lock (SyncRoot) + var filteredItems = items.Where(t => { - var filteredItems = items.Where(t => - { - if (CheckNullableItems && t.IsNull()) - throw new ArgumentNullException(nameof(t)); - - return OnAdding(t); - }).ToArray(); - InnerCollection.AddRange(filteredItems); - filteredItems.ForEach(base.OnAdded); - - AddedRange?.Invoke(filteredItems); - } - } + if (CheckNullableItems && t.IsNull()) + throw new ArgumentNullException(nameof(t)); - /// - /// Removes a range of items from the list in a thread-safe manner. - /// - /// The items to remove. - public void RemoveRange(IEnumerable items) - { - lock (SyncRoot) - { - var filteredItems = items.Where(OnRemoving).ToArray(); - InnerCollection.RemoveRange(filteredItems); - filteredItems.ForEach(base.OnRemoved); + return OnAdding(t); + }).ToArray(); + InnerCollection.AddRange(filteredItems); + filteredItems.ForEach(base.OnAdded); - RemovedRange?.Invoke(filteredItems); - } + AddedRange?.Invoke(filteredItems); } + } - /// - /// Removes a range of items from the list starting at the specified index, in a thread-safe manner. - /// - /// The zero-based starting index of the range to remove. - /// The number of items to remove. - /// The number of items actually removed. - /// Thrown when is less than -1 or is less than or equal to zero. - public int RemoveRange(int index, int count) + /// + /// Removes a range of items from the list in a thread-safe manner. + /// + /// The items to remove. + public void RemoveRange(IEnumerable items) + { + lock (SyncRoot) { - if (index < -1) - throw new ArgumentOutOfRangeException(nameof(index)); - - if (count <= 0) - throw new ArgumentOutOfRangeException(nameof(count)); + var filteredItems = items.Where(OnRemoving).ToArray(); + InnerCollection.RemoveRange(filteredItems); + filteredItems.ForEach(base.OnRemoved); - lock (SyncRoot) - { - var realCount = Count; - realCount -= index; - InnerCollection.RemoveRange(index, count); - return (realCount.Min(count)).Max(0); - } + RemovedRange?.Invoke(filteredItems); } + } - /// - /// Retrieves a range of items from the list starting at the specified index, in a thread-safe manner. - /// - /// The zero-based starting index of the range to retrieve. - /// The number of items to retrieve. - /// An enumerable containing the specified range of items. - public IEnumerable GetRange(int index, int count) + /// + /// Removes a range of items from the list starting at the specified index, in a thread-safe manner. + /// + /// The zero-based starting index of the range to remove. + /// The number of items to remove. + /// The number of items actually removed. + /// Thrown when is less than -1 or is less than or equal to zero. + public int RemoveRange(int index, int count) + { + if (index < -1) + throw new ArgumentOutOfRangeException(nameof(index)); + + if (count <= 0) + throw new ArgumentOutOfRangeException(nameof(count)); + + lock (SyncRoot) { - lock (SyncRoot) - return InnerCollection.GetRange(index, count); + var realCount = Count; + realCount -= index; + InnerCollection.RemoveRange(index, count); + return (realCount.Min(count)).Max(0); } } + + /// + /// Retrieves a range of items from the list starting at the specified index, in a thread-safe manner. + /// + /// The zero-based starting index of the range to retrieve. + /// The number of items to retrieve. + /// An enumerable containing the specified range of items. + public IEnumerable GetRange(int index, int count) + { + lock (SyncRoot) + return InnerCollection.GetRange(index, count); + } } \ No newline at end of file diff --git a/Collections/SynchronizedOrderedDictionary.cs b/Collections/SynchronizedOrderedDictionary.cs index 8e994b8e..513552fd 100644 --- a/Collections/SynchronizedOrderedDictionary.cs +++ b/Collections/SynchronizedOrderedDictionary.cs @@ -1,37 +1,36 @@ -namespace Ecng.Collections -{ - using System; - using System.Collections.Generic; +namespace Ecng.Collections; + +using System; +using System.Collections.Generic; +/// +/// Provides a thread-safe dictionary with keys ordered by a specified comparer. +/// +public class SynchronizedOrderedDictionary : SynchronizedDictionary +{ /// - /// Provides a thread-safe dictionary with keys ordered by a specified comparer. + /// Initializes a new instance of the class using the default comparer. /// - public class SynchronizedOrderedDictionary : SynchronizedDictionary + public SynchronizedOrderedDictionary() + : base(new SortedDictionary()) { - /// - /// Initializes a new instance of the class using the default comparer. - /// - public SynchronizedOrderedDictionary() - : base(new SortedDictionary()) - { - } + } - /// - /// Initializes a new instance of the class using the specified comparer. - /// - /// The comparer used to sort the keys. - public SynchronizedOrderedDictionary(IComparer comparer) - : base(new SortedDictionary(comparer)) - { - } + /// + /// Initializes a new instance of the class using the specified comparer. + /// + /// The comparer used to sort the keys. + public SynchronizedOrderedDictionary(IComparer comparer) + : base(new SortedDictionary(comparer)) + { + } - /// - /// Initializes a new instance of the class using a custom comparison function. - /// - /// A function that compares two keys and returns an integer indicating their relative order. - public SynchronizedOrderedDictionary(Func comparer) - : this(comparer.ToComparer()) - { - } + /// + /// Initializes a new instance of the class using a custom comparison function. + /// + /// A function that compares two keys and returns an integer indicating their relative order. + public SynchronizedOrderedDictionary(Func comparer) + : this(comparer.ToComparer()) + { } } \ No newline at end of file diff --git a/Collections/SynchronizedPairSet.cs b/Collections/SynchronizedPairSet.cs index 115db48b..bb8b3cbf 100644 --- a/Collections/SynchronizedPairSet.cs +++ b/Collections/SynchronizedPairSet.cs @@ -1,268 +1,267 @@ -namespace Ecng.Collections +namespace Ecng.Collections; + +using System; +using System.Collections.Generic; + +/// +/// Represents a thread-safe collection that maintains key/value pairs and allows bidirectional lookups. +/// +[Serializable] +public class SynchronizedPairSet : SynchronizedKeyedCollection { - using System; - using System.Collections.Generic; + #region Private Fields + + private readonly Dictionary _values; + + #endregion /// - /// Represents a thread-safe collection that maintains key/value pairs and allows bidirectional lookups. + /// Initializes a new instance of the class. /// - [Serializable] - public class SynchronizedPairSet : SynchronizedKeyedCollection + public SynchronizedPairSet() { - #region Private Fields - - private readonly Dictionary _values; + _values = []; + } - #endregion + /// + /// Initializes a new instance of the class with a specified comparer for keys. + /// + /// An equality comparer for keys. + public SynchronizedPairSet(IEqualityComparer comparer) + : base(comparer) + { + _values = []; + } - /// - /// Initializes a new instance of the class. - /// - public SynchronizedPairSet() - { - _values = []; - } + /// + /// Initializes a new instance of the class with specified comparers for keys and values. + /// + /// An equality comparer for keys. + /// An equality comparer for values. + public SynchronizedPairSet(IEqualityComparer keyComparer, IEqualityComparer valueComparer) + : base(keyComparer) + { + _values = new Dictionary(valueComparer); + } - /// - /// Initializes a new instance of the class with a specified comparer for keys. - /// - /// An equality comparer for keys. - public SynchronizedPairSet(IEqualityComparer comparer) - : base(comparer) - { - _values = []; - } + /// + /// Adds the specified key and value to the collection. + /// + /// The key to insert. + /// The value to insert. + public override void Add(TKey key, TValue value) + { + lock (SyncRoot) + base.Add(key, value); + } - /// - /// Initializes a new instance of the class with specified comparers for keys and values. - /// - /// An equality comparer for keys. - /// An equality comparer for values. - public SynchronizedPairSet(IEqualityComparer keyComparer, IEqualityComparer valueComparer) - : base(keyComparer) - { - _values = new Dictionary(valueComparer); - } + #region Item - /// - /// Adds the specified key and value to the collection. - /// - /// The key to insert. - /// The value to insert. - public override void Add(TKey key, TValue value) + /// + /// Gets the key associated with the specified value. + /// + /// The value for which to retrieve the key. + /// The key corresponding to the specified value. + public TKey this[TValue value] + { + get { lock (SyncRoot) - base.Add(key, value); - } - - #region Item - - /// - /// Gets the key associated with the specified value. - /// - /// The value for which to retrieve the key. - /// The key corresponding to the specified value. - public TKey this[TValue value] - { - get - { - lock (SyncRoot) - return _values[value]; - } + return _values[value]; } + } - #endregion + #endregion - #region GetKey + #region GetKey - /// - /// Retrieves the key associated with the specified value. - /// - /// The value whose key is requested. - /// The key associated with the given value. - public TKey GetKey(TValue value) - { - return this[value]; - } + /// + /// Retrieves the key associated with the specified value. + /// + /// The value whose key is requested. + /// The key associated with the given value. + public TKey GetKey(TValue value) + { + return this[value]; + } - #endregion + #endregion - #region GetValue + #region GetValue - /// - /// Retrieves the value associated with the specified key. - /// - /// The key whose value is requested. - /// The value associated with the given key. - public TValue GetValue(TKey key) - { - return base[key]; - } + /// + /// Retrieves the value associated with the specified key. + /// + /// The key whose value is requested. + /// The value associated with the given key. + public TValue GetValue(TKey key) + { + return base[key]; + } - #endregion + #endregion - /// - /// Removes the old pair for the specified key and adds the new key/value pair. Raises an event when setting. - /// - /// The key to set. - /// The value to associate with the key. - public void SetKey(TKey key, TValue value) + /// + /// Removes the old pair for the specified key and adds the new key/value pair. Raises an event when setting. + /// + /// The key to set. + /// The value to associate with the key. + public void SetKey(TKey key, TValue value) + { + lock (SyncRoot) { - lock (SyncRoot) - { - Remove(key); + Remove(key); - Add(key, value); - OnSetting(key, value); - } + Add(key, value); + OnSetting(key, value); } + } - #region SetValue + #region SetValue - /// - /// Updates the value associated with a key. - /// - /// The key to update. - /// The new value. - public void SetValue(TKey key, TValue value) - { - lock (SyncRoot) - this[key] = value; - } + /// + /// Updates the value associated with a key. + /// + /// The key to update. + /// The new value. + public void SetValue(TKey key, TValue value) + { + lock (SyncRoot) + this[key] = value; + } - #endregion + #endregion - /// - /// Determines whether a key/value pair can be added based on whether the value is already present. - /// - /// The key to be added. - /// The value to be added. - /// true if the value is not already contained; otherwise false. - protected override bool CanAdd(TKey key, TValue value) - { - return !_values.ContainsKey(value); - } + /// + /// Determines whether a key/value pair can be added based on whether the value is already present. + /// + /// The key to be added. + /// The value to be added. + /// true if the value is not already contained; otherwise false. + protected override bool CanAdd(TKey key, TValue value) + { + return !_values.ContainsKey(value); + } - #region SynchronizedKeyedCollection Members + #region SynchronizedKeyedCollection Members - /// - /// Called when an item is being added. Adds the value/key mapping to the internal dictionary. - /// - /// The key being added. - /// The value being added. - protected override void OnAdding(TKey key, TValue value) - { - _values.Add(value, key); - } + /// + /// Called when an item is being added. Adds the value/key mapping to the internal dictionary. + /// + /// The key being added. + /// The value being added. + protected override void OnAdding(TKey key, TValue value) + { + _values.Add(value, key); + } - /// - /// Called when an existing item is being set. Updates the value/key mapping in the internal dictionary. - /// - /// The key being set. - /// The value being set. - protected override void OnSetting(TKey key, TValue value) - { - _values[value] = key; - } + /// + /// Called when an existing item is being set. Updates the value/key mapping in the internal dictionary. + /// + /// The key being set. + /// The value being set. + protected override void OnSetting(TKey key, TValue value) + { + _values[value] = key; + } - /// - /// Called when the collection is being cleared. Clears the internal dictionary. - /// - protected override void OnClearing() - { - _values.Clear(); - } + /// + /// Called when the collection is being cleared. Clears the internal dictionary. + /// + protected override void OnClearing() + { + _values.Clear(); + } - /// - /// Called when an item is being removed. Removes the value/key mapping from the internal dictionary. - /// - /// The key being removed. - /// The value being removed. - protected override void OnRemoving(TKey key, TValue value) - { - _values.Remove(value); - } + /// + /// Called when an item is being removed. Removes the value/key mapping from the internal dictionary. + /// + /// The key being removed. + /// The value being removed. + protected override void OnRemoving(TKey key, TValue value) + { + _values.Remove(value); + } - #endregion + #endregion - /// - /// Attempts to add a new key/value pair if neither key nor value is already present. - /// - /// The key to add. - /// The value to add. - /// true if the pair was added; otherwise false. - public bool TryAdd(TKey key, TValue value) + /// + /// Attempts to add a new key/value pair if neither key nor value is already present. + /// + /// The key to add. + /// The value to add. + /// true if the pair was added; otherwise false. + public bool TryAdd(TKey key, TValue value) + { + lock (SyncRoot) { - lock (SyncRoot) - { - if (ContainsKey(key) || _values.ContainsKey(value)) - return false; + if (ContainsKey(key) || _values.ContainsKey(value)) + return false; - Add(key, value); - return true; - } + Add(key, value); + return true; } + } - /// - /// Attempts to get a key for the specified value. - /// - /// The value whose key is requested. - /// The key if found; otherwise the default. - public TKey TryGetKey(TValue value) - { - lock (SyncRoot) - return _values.TryGetValue(value); - } + /// + /// Attempts to get a key for the specified value. + /// + /// The value whose key is requested. + /// The key if found; otherwise the default. + public TKey TryGetKey(TValue value) + { + lock (SyncRoot) + return _values.TryGetValue(value); + } - /// - /// Attempts to get a key for the specified value, returning a boolean indicating success. - /// - /// The value whose key is requested. - /// When this method returns, contains the key if found. - /// true if the key was found; otherwise false. - public bool TryGetKey(TValue value, out TKey key) - { - lock (SyncRoot) - return _values.TryGetValue(value, out key); - } + /// + /// Attempts to get a key for the specified value, returning a boolean indicating success. + /// + /// The value whose key is requested. + /// When this method returns, contains the key if found. + /// true if the key was found; otherwise false. + public bool TryGetKey(TValue value, out TKey key) + { + lock (SyncRoot) + return _values.TryGetValue(value, out key); + } - /// - /// Attempts to get and remove the key associated with the specified value. - /// - /// The value for which to retrieve and remove the key. - /// When this method returns, contains the removed key if found. - /// true if the key was removed successfully; otherwise false. - public bool TryGetKeyAndRemove(TValue value, out TKey key) + /// + /// Attempts to get and remove the key associated with the specified value. + /// + /// The value for which to retrieve and remove the key. + /// When this method returns, contains the removed key if found. + /// true if the key was removed successfully; otherwise false. + public bool TryGetKeyAndRemove(TValue value, out TKey key) + { + lock (SyncRoot) { - lock (SyncRoot) - { - if (!_values.TryGetAndRemove(value, out key)) - return false; + if (!_values.TryGetAndRemove(value, out key)) + return false; - return base.Remove(key); - } + return base.Remove(key); } + } - /// - /// Removes the element with the specified value if it is found. - /// - /// The value to remove. - /// true if the item was removed; otherwise false. - public bool RemoveByValue(TValue value) - { - lock (SyncRoot) - return _values.ContainsKey(value) && Remove(_values[value]); - } + /// + /// Removes the element with the specified value if it is found. + /// + /// The value to remove. + /// true if the item was removed; otherwise false. + public bool RemoveByValue(TValue value) + { + lock (SyncRoot) + return _values.ContainsKey(value) && Remove(_values[value]); + } - /// - /// Determines whether the collection contains an entry with the specified value. - /// - /// The value to locate. - /// true if the value is found; otherwise false. - public bool ContainsValue(TValue value) - { - lock (SyncRoot) - return _values.ContainsKey(value); - } + /// + /// Determines whether the collection contains an entry with the specified value. + /// + /// The value to locate. + /// true if the value is found; otherwise false. + public bool ContainsValue(TValue value) + { + lock (SyncRoot) + return _values.ContainsKey(value); } } \ No newline at end of file diff --git a/Collections/SynchronizedQueue.cs b/Collections/SynchronizedQueue.cs index bc1a52a9..79eb38ab 100644 --- a/Collections/SynchronizedQueue.cs +++ b/Collections/SynchronizedQueue.cs @@ -1,93 +1,92 @@ -namespace Ecng.Collections -{ - using System; +namespace Ecng.Collections; + +using System; +/// +/// Provides a thread-safe queue based on the collection. +/// +/// The type of elements in the queue. +[Serializable] +public class SynchronizedQueue : SynchronizedCollection> +{ /// - /// Provides a thread-safe queue based on the collection. + /// Initializes a new instance of the class. /// - /// The type of elements in the queue. - [Serializable] - public class SynchronizedQueue : SynchronizedCollection> + public SynchronizedQueue() + : base(new QueueEx()) { - /// - /// Initializes a new instance of the class. - /// - public SynchronizedQueue() - : base(new QueueEx()) - { - } + } - /// - /// Adds an item to the queue in a thread-safe manner. - /// - /// The item to enqueue. - public void Enqueue(T item) - { - lock (SyncRoot) - InnerCollection.Enqueue(item); - } + /// + /// Adds an item to the queue in a thread-safe manner. + /// + /// The item to enqueue. + public void Enqueue(T item) + { + lock (SyncRoot) + InnerCollection.Enqueue(item); + } - /// - /// Removes and returns the item at the beginning of the queue in a thread-safe manner. - /// - /// The item that was removed from the queue. - public T Dequeue() - { - lock (SyncRoot) - return InnerCollection.Dequeue(); - } + /// + /// Removes and returns the item at the beginning of the queue in a thread-safe manner. + /// + /// The item that was removed from the queue. + public T Dequeue() + { + lock (SyncRoot) + return InnerCollection.Dequeue(); + } - /// - /// Returns the item at the beginning of the queue without removing it, in a thread-safe manner. - /// - /// The item at the beginning of the queue. - public T Peek() - { - lock (SyncRoot) - return InnerCollection.Peek(); - } + /// + /// Returns the item at the beginning of the queue without removing it, in a thread-safe manner. + /// + /// The item at the beginning of the queue. + public T Peek() + { + lock (SyncRoot) + return InnerCollection.Peek(); + } - /// - /// Throws a as this operation is not supported for a queue. - /// - /// The index of the item to retrieve. - /// This method always throws an exception. - /// This operation is not supported for a queue. - protected override T OnGetItem(int index) - { - throw new NotSupportedException(); - } + /// + /// Throws a as this operation is not supported for a queue. + /// + /// The index of the item to retrieve. + /// This method always throws an exception. + /// This operation is not supported for a queue. + protected override T OnGetItem(int index) + { + throw new NotSupportedException(); + } - /// - /// Throws a as this operation is not supported for a queue. - /// - /// The index at which the item should be inserted. - /// The item to insert. - /// This operation is not supported for a queue. - protected override void OnInsert(int index, T item) - { - throw new NotSupportedException(); - } + /// + /// Throws a as this operation is not supported for a queue. + /// + /// The index at which the item should be inserted. + /// The item to insert. + /// This operation is not supported for a queue. + protected override void OnInsert(int index, T item) + { + throw new NotSupportedException(); + } - /// - /// Throws a as this operation is not supported for a queue. - /// - /// The index of the item to remove. - /// This operation is not supported for a queue. - protected override void OnRemoveAt(int index) - { - throw new NotSupportedException(); - } + /// + /// Throws a as this operation is not supported for a queue. + /// + /// The index of the item to remove. + /// This operation is not supported for a queue. + protected override void OnRemoveAt(int index) + { + throw new NotSupportedException(); + } - /// - /// Throws a as this operation is not supported for a queue. - /// - /// The item to locate in the queue. - /// This method always throws an exception. - /// This operation is not supported for a queue. - protected override int OnIndexOf(T item) - { - throw new NotSupportedException(); - } + /// + /// Throws a as this operation is not supported for a queue. + /// + /// The item to locate in the queue. + /// This method always throws an exception. + /// This operation is not supported for a queue. + protected override int OnIndexOf(T item) + { + throw new NotSupportedException(); } } \ No newline at end of file diff --git a/Collections/SynchronizedSet.cs b/Collections/SynchronizedSet.cs index 6449c070..fba3e93c 100644 --- a/Collections/SynchronizedSet.cs +++ b/Collections/SynchronizedSet.cs @@ -1,466 +1,465 @@ -namespace Ecng.Collections -{ - using System; - using System.Collections.Generic; - using System.Linq; +namespace Ecng.Collections; + +using System; +using System.Collections.Generic; +using System.Linq; - using Ecng.Common; +using Ecng.Common; + +/// +/// Represents a thread-safe set that supports optional indexing and range-based operations. +/// +/// The element type. +[Serializable] +public class SynchronizedSet : SynchronizedCollection>, ISet, ICollectionEx +{ + private readonly PairSet _indecies; + private int _maxIndex = -1; + private bool _raiseRangeEvents = true; /// - /// Represents a thread-safe set that supports optional indexing and range-based operations. + /// Initializes a new instance of the class. /// - /// The element type. - [Serializable] - public class SynchronizedSet : SynchronizedCollection>, ISet, ICollectionEx - { - private readonly PairSet _indecies; - private int _maxIndex = -1; - private bool _raiseRangeEvents = true; - - /// - /// Initializes a new instance of the class. - /// - public SynchronizedSet() - : this(false) - { - } + public SynchronizedSet() + : this(false) + { + } - /// - /// Initializes a new instance of the class with an option to enable indexing. - /// - /// True to enable indexing; otherwise, false. - public SynchronizedSet(bool allowIndexing) - : this(allowIndexing, EqualityComparer.Default) - { - } + /// + /// Initializes a new instance of the class with an option to enable indexing. + /// + /// True to enable indexing; otherwise, false. + public SynchronizedSet(bool allowIndexing) + : this(allowIndexing, EqualityComparer.Default) + { + } - /// - /// Initializes a new instance of the class with a specified comparer. - /// - /// The comparer to use for comparing elements. - public SynchronizedSet(IEqualityComparer comparer) - : this(false, comparer) - { - } + /// + /// Initializes a new instance of the class with a specified comparer. + /// + /// The comparer to use for comparing elements. + public SynchronizedSet(IEqualityComparer comparer) + : this(false, comparer) + { + } - /// - /// Initializes a new instance of the class with indexing and a custom comparer. - /// - /// True to enable indexing; otherwise, false. - /// The comparer to use for comparing elements. - public SynchronizedSet(bool allowIndexing, IEqualityComparer comparer) - : this(allowIndexing, new HashSet(comparer)) - { - } + /// + /// Initializes a new instance of the class with indexing and a custom comparer. + /// + /// True to enable indexing; otherwise, false. + /// The comparer to use for comparing elements. + public SynchronizedSet(bool allowIndexing, IEqualityComparer comparer) + : this(allowIndexing, new HashSet(comparer)) + { + } - /// - /// Initializes a new instance of the class from an existing collection. - /// - /// The collection whose elements are copied to the new set. - public SynchronizedSet(IEnumerable collection) - : this(collection, EqualityComparer.Default) - { - } + /// + /// Initializes a new instance of the class from an existing collection. + /// + /// The collection whose elements are copied to the new set. + public SynchronizedSet(IEnumerable collection) + : this(collection, EqualityComparer.Default) + { + } - /// - /// Initializes a new instance of the class from an existing collection and comparer. - /// - /// The collection whose elements are copied to the new set. - /// The comparer to use for comparing elements. - public SynchronizedSet(IEnumerable collection, IEqualityComparer comparer) - : this(false, collection, comparer) - { - } + /// + /// Initializes a new instance of the class from an existing collection and comparer. + /// + /// The collection whose elements are copied to the new set. + /// The comparer to use for comparing elements. + public SynchronizedSet(IEnumerable collection, IEqualityComparer comparer) + : this(false, collection, comparer) + { + } - /// - /// Initializes a new instance of the class with control over indexing, an existing collection, and comparer. - /// - /// True to enable indexing; otherwise, false. - /// The collection whose elements are copied to the new set. - /// The comparer to use for comparing elements. - public SynchronizedSet(bool allowIndexing, IEnumerable collection, IEqualityComparer comparer) - : this(allowIndexing, new HashSet(collection, comparer)) - { - } + /// + /// Initializes a new instance of the class with control over indexing, an existing collection, and comparer. + /// + /// True to enable indexing; otherwise, false. + /// The collection whose elements are copied to the new set. + /// The comparer to use for comparing elements. + public SynchronizedSet(bool allowIndexing, IEnumerable collection, IEqualityComparer comparer) + : this(allowIndexing, new HashSet(collection, comparer)) + { + } - /// - /// Initializes a new instance of the class using the specified set as the inner collection. - /// - /// True to enable indexing; otherwise, false. - /// The inner set for the synchronized collection. - protected SynchronizedSet(bool allowIndexing, ISet innerCollection) - : base(innerCollection) - { - if (allowIndexing) - _indecies = []; - } + /// + /// Initializes a new instance of the class using the specified set as the inner collection. + /// + /// True to enable indexing; otherwise, false. + /// The inner set for the synchronized collection. + protected SynchronizedSet(bool allowIndexing, ISet innerCollection) + : base(innerCollection) + { + if (allowIndexing) + _indecies = []; + } - /// - /// Gets or sets a value indicating whether an exception is thrown when adding a duplicate element. - /// - public bool ThrowIfDuplicate { get; set; } + /// + /// Gets or sets a value indicating whether an exception is thrown when adding a duplicate element. + /// + public bool ThrowIfDuplicate { get; set; } - private void Duplicate() - { - if (ThrowIfDuplicate) - throw new InvalidOperationException("Duplicate element."); - } + private void Duplicate() + { + if (ThrowIfDuplicate) + throw new InvalidOperationException("Duplicate element."); + } - private void CheckIndexingEnabled() - { - if (_indecies is null) - throw new InvalidOperationException("Indexing not switched on."); - } + private void CheckIndexingEnabled() + { + if (_indecies is null) + throw new InvalidOperationException("Indexing not switched on."); + } - private void AddIndicies(T item) - { - if (_indecies is null) - return; + private void AddIndicies(T item) + { + if (_indecies is null) + return; - _maxIndex = Count - 1; - _indecies.Add(_maxIndex, item); - } + _maxIndex = Count - 1; + _indecies.Add(_maxIndex, item); + } - private bool RemoveIndicies(T item) - { - if (_indecies is null) - return true; + private bool RemoveIndicies(T item) + { + if (_indecies is null) + return true; - if (_maxIndex == -1) - throw new InvalidOperationException(); + if (_maxIndex == -1) + throw new InvalidOperationException(); - var index = _indecies.GetKey(item); - _indecies.RemoveByValue(item); + var index = _indecies.GetKey(item); + _indecies.RemoveByValue(item); - for (var i = index + 1; i <= _maxIndex; i++) - { - _indecies.SetKey(_indecies[i], i - 1); - } + for (var i = index + 1; i <= _maxIndex; i++) + { + _indecies.SetKey(_indecies[i], i - 1); + } - _maxIndex--; + _maxIndex--; - return true; - } + return true; + } - /// - protected override bool OnAdding(T item) + /// + protected override bool OnAdding(T item) + { + if (InnerCollection.Contains(item)) { - if (InnerCollection.Contains(item)) - { - Duplicate(); - return false; - } - - return base.OnAdding(item); + Duplicate(); + return false; } - /// - protected override T OnGetItem(int index) - { - CheckIndexingEnabled(); + return base.OnAdding(item); + } - return _indecies[index]; - } + /// + protected override T OnGetItem(int index) + { + CheckIndexingEnabled(); - /// - protected override void OnInsert(int index, T item) - { - if (!InnerCollection.Add(item)) - return; + return _indecies[index]; + } - if (_indecies is null) - return; + /// + protected override void OnInsert(int index, T item) + { + if (!InnerCollection.Add(item)) + return; - if (_maxIndex == -1) - throw new InvalidOperationException(); + if (_indecies is null) + return; - for (var i = _maxIndex; i >= index; i--) - _indecies.SetKey(_indecies[i], i + 1); + if (_maxIndex == -1) + throw new InvalidOperationException(); - _indecies[index] = item; - _maxIndex++; - } + for (var i = _maxIndex; i >= index; i--) + _indecies.SetKey(_indecies[i], i + 1); - /// - protected override void OnRemoveAt(int index) - { - CheckIndexingEnabled(); + _indecies[index] = item; + _maxIndex++; + } - if (_indecies.ContainsKey(index)) - Remove(_indecies.GetValue(index)); - } + /// + protected override void OnRemoveAt(int index) + { + CheckIndexingEnabled(); - /// - protected override void OnAdd(T item) - { - if (!InnerCollection.Add(item)) - return; + if (_indecies.ContainsKey(index)) + Remove(_indecies.GetValue(index)); + } - AddIndicies(item); - } + /// + protected override void OnAdd(T item) + { + if (!InnerCollection.Add(item)) + return; - /// - protected override bool OnRemove(T item) - { - if (!base.OnRemove(item)) - return false; + AddIndicies(item); + } - return RemoveIndicies(item); - } + /// + protected override bool OnRemove(T item) + { + if (!base.OnRemove(item)) + return false; - /// - protected override void OnClear() - { - base.OnClear(); + return RemoveIndicies(item); + } - _indecies?.Clear(); - _maxIndex = -1; - } + /// + protected override void OnClear() + { + base.OnClear(); - /// - protected override int OnIndexOf(T item) - { - CheckIndexingEnabled(); + _indecies?.Clear(); + _maxIndex = -1; + } - return _indecies.GetKey(item); - } + /// + protected override int OnIndexOf(T item) + { + CheckIndexingEnabled(); - #region Implementation of ISet + return _indecies.GetKey(item); + } - /// - /// Modifies the current set so that it contains all elements that are present in both the current set and in the specified collection. - /// - /// The collection to compare to the current set. - public void UnionWith(IEnumerable other) - { - AddRange(other); - } + #region Implementation of ISet - /// - /// Modifies the current set so that it contains only elements that are also in a specified collection. - /// - /// The collection to compare to the current set. - public void IntersectWith(IEnumerable other) - { - throw new NotImplementedException(); - } + /// + /// Modifies the current set so that it contains all elements that are present in both the current set and in the specified collection. + /// + /// The collection to compare to the current set. + public void UnionWith(IEnumerable other) + { + AddRange(other); + } - /// - /// Removes all elements in the specified collection from the current set. - /// - /// The collection of items to remove from the set. - public void ExceptWith(IEnumerable other) - { - RemoveRange(other); - } + /// + /// Modifies the current set so that it contains only elements that are also in a specified collection. + /// + /// The collection to compare to the current set. + public void IntersectWith(IEnumerable other) + { + throw new NotImplementedException(); + } - /// - /// Modifies the current set so that it contains only elements that are present either in the current set or in the specified collection, but not both. - /// - /// The collection to compare to the current set. - public void SymmetricExceptWith(IEnumerable other) - { - throw new NotImplementedException(); - } + /// + /// Removes all elements in the specified collection from the current set. + /// + /// The collection of items to remove from the set. + public void ExceptWith(IEnumerable other) + { + RemoveRange(other); + } - /// - /// Determines whether the current set is a subset of a specified collection. - /// - /// The collection to compare to the current set. - /// True if the set is a subset of other; otherwise, false. - public bool IsSubsetOf(IEnumerable other) - { - lock (SyncRoot) - return InnerCollection.IsSubsetOf(other); - } + /// + /// Modifies the current set so that it contains only elements that are present either in the current set or in the specified collection, but not both. + /// + /// The collection to compare to the current set. + public void SymmetricExceptWith(IEnumerable other) + { + throw new NotImplementedException(); + } - /// - /// Determines whether the current set is a superset of a specified collection. - /// - /// The collection to compare to the current set. - /// True if the set is a superset of other; otherwise, false. - public bool IsSupersetOf(IEnumerable other) - { - lock (SyncRoot) - return InnerCollection.IsSupersetOf(other); - } + /// + /// Determines whether the current set is a subset of a specified collection. + /// + /// The collection to compare to the current set. + /// True if the set is a subset of other; otherwise, false. + public bool IsSubsetOf(IEnumerable other) + { + lock (SyncRoot) + return InnerCollection.IsSubsetOf(other); + } - /// - /// Determines whether the current set is a proper superset of a specified collection. - /// - /// The collection to compare to the current set. - /// True if the set is a proper superset of other; otherwise, false. - public bool IsProperSupersetOf(IEnumerable other) - { - lock (SyncRoot) - return InnerCollection.Overlaps(other); - } + /// + /// Determines whether the current set is a superset of a specified collection. + /// + /// The collection to compare to the current set. + /// True if the set is a superset of other; otherwise, false. + public bool IsSupersetOf(IEnumerable other) + { + lock (SyncRoot) + return InnerCollection.IsSupersetOf(other); + } - /// - /// Determines whether the current set is a proper subset of a specified collection. - /// - /// The collection to compare to the current set. - /// True if the set is a proper subset of other; otherwise, false. - public bool IsProperSubsetOf(IEnumerable other) - { - lock (SyncRoot) - return InnerCollection.IsProperSubsetOf(other); - } + /// + /// Determines whether the current set is a proper superset of a specified collection. + /// + /// The collection to compare to the current set. + /// True if the set is a proper superset of other; otherwise, false. + public bool IsProperSupersetOf(IEnumerable other) + { + lock (SyncRoot) + return InnerCollection.Overlaps(other); + } - /// - /// Determines whether the current set overlaps with the specified collection. - /// - /// The collection to compare to the current set. - /// True if the sets overlap; otherwise, false. - public bool Overlaps(IEnumerable other) - { - lock (SyncRoot) - return InnerCollection.Overlaps(other); - } + /// + /// Determines whether the current set is a proper subset of a specified collection. + /// + /// The collection to compare to the current set. + /// True if the set is a proper subset of other; otherwise, false. + public bool IsProperSubsetOf(IEnumerable other) + { + lock (SyncRoot) + return InnerCollection.IsProperSubsetOf(other); + } - /// - /// Determines whether the current set and a specified collection contain the same elements. - /// - /// The collection to compare to the current set. - /// True if the sets contain the same elements; otherwise, false. - public bool SetEquals(IEnumerable other) - { - throw new NotImplementedException(); - } + /// + /// Determines whether the current set overlaps with the specified collection. + /// + /// The collection to compare to the current set. + /// True if the sets overlap; otherwise, false. + public bool Overlaps(IEnumerable other) + { + lock (SyncRoot) + return InnerCollection.Overlaps(other); + } - /// - /// Adds an item to the current set and returns a value to indicate if the item was successfully added. - /// - /// The element to add to the set. - /// True if the item is added to the set; otherwise, false. - bool ISet.Add(T item) - { - return TryAdd(item); - } + /// + /// Determines whether the current set and a specified collection contain the same elements. + /// + /// The collection to compare to the current set. + /// True if the sets contain the same elements; otherwise, false. + public bool SetEquals(IEnumerable other) + { + throw new NotImplementedException(); + } + + /// + /// Adds an item to the current set and returns a value to indicate if the item was successfully added. + /// + /// The element to add to the set. + /// True if the item is added to the set; otherwise, false. + bool ISet.Add(T item) + { + return TryAdd(item); + } - #endregion + #endregion - /// - /// Attempts to add the specified item to the set without throwing an exception for duplicates. - /// - /// The item to add. - /// True if the item was added; otherwise, false. - public bool TryAdd(T item) + /// + /// Attempts to add the specified item to the set without throwing an exception for duplicates. + /// + /// The item to add. + /// True if the item was added; otherwise, false. + public bool TryAdd(T item) + { + lock (SyncRoot) { - lock (SyncRoot) + if (InnerCollection.Contains(item)) { - if (InnerCollection.Contains(item)) - { - Duplicate(); - return false; - } - - Add(item); - return true; + Duplicate(); + return false; } + + Add(item); + return true; } + } - /// - /// Occurs when multiple items have been added to the set. - /// - public event Action> AddedRange; + /// + /// Occurs when multiple items have been added to the set. + /// + public event Action> AddedRange; - /// - /// Occurs when multiple items have been removed from the set. - /// - public event Action> RemovedRange; + /// + /// Occurs when multiple items have been removed from the set. + /// + public event Action> RemovedRange; - /// - protected override void OnAdded(T item) - { - base.OnAdded(item); + /// + protected override void OnAdded(T item) + { + base.OnAdded(item); - if (_raiseRangeEvents) - AddedRange?.Invoke([item]); - } + if (_raiseRangeEvents) + AddedRange?.Invoke([item]); + } - /// - protected override void OnRemoved(T item) - { - base.OnRemoved(item); + /// + protected override void OnRemoved(T item) + { + base.OnRemoved(item); - if (_raiseRangeEvents) - RemovedRange?.Invoke([item]); - } + if (_raiseRangeEvents) + RemovedRange?.Invoke([item]); + } - /// - /// Adds a range of items to the set. - /// - /// The items to add. - public void AddRange(IEnumerable items) + /// + /// Adds a range of items to the set. + /// + /// The items to add. + public void AddRange(IEnumerable items) + { + lock (SyncRoot) { - lock (SyncRoot) + var filteredItems = items.Where(t => { - var filteredItems = items.Where(t => - { - if (CheckNullableItems && t.IsNull()) - throw new ArgumentNullException(nameof(t)); + if (CheckNullableItems && t.IsNull()) + throw new ArgumentNullException(nameof(t)); - return OnAdding(t); - }).ToArray(); - InnerCollection.AddRange(filteredItems); + return OnAdding(t); + }).ToArray(); + InnerCollection.AddRange(filteredItems); - ProcessRange(filteredItems, item => - { - //AddIndicies(item); + ProcessRange(filteredItems, item => + { + //AddIndicies(item); - if (_indecies != null) - { - _indecies.Add(_maxIndex + 1, item); - _maxIndex++; - } + if (_indecies != null) + { + _indecies.Add(_maxIndex + 1, item); + _maxIndex++; + } - OnAdded(item); - }); + OnAdded(item); + }); - AddedRange?.Invoke(filteredItems); - } + AddedRange?.Invoke(filteredItems); } + } - /// - /// Removes a range of items from the set. - /// - /// The items to remove. - public void RemoveRange(IEnumerable items) + /// + /// Removes a range of items from the set. + /// + /// The items to remove. + public void RemoveRange(IEnumerable items) + { + lock (SyncRoot) { - lock (SyncRoot) + var filteredItems = items.Where(OnRemoving).ToArray(); + InnerCollection.RemoveRange(filteredItems); + ProcessRange(filteredItems, item => { - var filteredItems = items.Where(OnRemoving).ToArray(); - InnerCollection.RemoveRange(filteredItems); - ProcessRange(filteredItems, item => - { - RemoveIndicies(item); - OnRemoved(item); - }); + RemoveIndicies(item); + OnRemoved(item); + }); - RemovedRange?.Invoke(filteredItems); - } + RemovedRange?.Invoke(filteredItems); } + } - private void ProcessRange(IEnumerable items, Action action) - { - _raiseRangeEvents = false; + private void ProcessRange(IEnumerable items, Action action) + { + _raiseRangeEvents = false; - items.ForEach(action); + items.ForEach(action); - _raiseRangeEvents = true; - } + _raiseRangeEvents = true; + } - /// - /// Removes a specified number of items starting at the given index. Not yet implemented. - /// - /// The starting index. - /// The number of items to remove. - /// The number of items removed. - /// Always thrown. - public int RemoveRange(int index, int count) - { - throw new NotImplementedException(); - } + /// + /// Removes a specified number of items starting at the given index. Not yet implemented. + /// + /// The starting index. + /// The number of items to remove. + /// The number of items removed. + /// Always thrown. + public int RemoveRange(int index, int count) + { + throw new NotImplementedException(); } } \ No newline at end of file diff --git a/Collections/SynchronizedStack.cs b/Collections/SynchronizedStack.cs index 8f6ce28b..3ad0810c 100644 --- a/Collections/SynchronizedStack.cs +++ b/Collections/SynchronizedStack.cs @@ -1,93 +1,92 @@ -namespace Ecng.Collections -{ - using System; +namespace Ecng.Collections; + +using System; +/// +/// Represents a synchronized stack of items. +/// +/// The type of elements in the stack. +[Serializable] +public class SynchronizedStack : SynchronizedCollection> +{ /// - /// Represents a synchronized stack of items. + /// Initializes a new instance of the class. /// - /// The type of elements in the stack. - [Serializable] - public class SynchronizedStack : SynchronizedCollection> + public SynchronizedStack() + : base(new StackEx()) { - /// - /// Initializes a new instance of the class. - /// - public SynchronizedStack() - : base(new StackEx()) - { - } + } - /// - /// Adds an item to the top of this stack. - /// - /// The item to add. - public void Push(T item) - { - lock (SyncRoot) - InnerCollection.Push(item); - } + /// + /// Adds an item to the top of this stack. + /// + /// The item to add. + public void Push(T item) + { + lock (SyncRoot) + InnerCollection.Push(item); + } - /// - /// Removes and returns the object at the top of this stack. - /// - /// The item removed from the top. - public T Pop() - { - lock (SyncRoot) - return InnerCollection.Pop(); - } + /// + /// Removes and returns the object at the top of this stack. + /// + /// The item removed from the top. + public T Pop() + { + lock (SyncRoot) + return InnerCollection.Pop(); + } - /// - /// Returns the object at the top of this stack without removing it. - /// - /// The item at the top. - public T Peek() - { - lock (SyncRoot) - return InnerCollection.Peek(); - } + /// + /// Returns the object at the top of this stack without removing it. + /// + /// The item at the top. + public T Peek() + { + lock (SyncRoot) + return InnerCollection.Peek(); + } - /// - /// Throws a as this operation is not supported for a stack. - /// - /// The index of the item to retrieve. - /// This method always throws an exception. - /// This operation is not supported for a stack. - protected override T OnGetItem(int index) - { - throw new NotSupportedException(); - } + /// + /// Throws a as this operation is not supported for a stack. + /// + /// The index of the item to retrieve. + /// This method always throws an exception. + /// This operation is not supported for a stack. + protected override T OnGetItem(int index) + { + throw new NotSupportedException(); + } - /// - /// Throws a as this operation is not supported for a stack. - /// - /// The index at which the item should be inserted. - /// The item to insert. - /// This operation is not supported for a stack. - protected override void OnInsert(int index, T item) - { - throw new NotSupportedException(); - } + /// + /// Throws a as this operation is not supported for a stack. + /// + /// The index at which the item should be inserted. + /// The item to insert. + /// This operation is not supported for a stack. + protected override void OnInsert(int index, T item) + { + throw new NotSupportedException(); + } - /// - /// Throws a as this operation is not supported for a stack. - /// - /// The index of the item to remove. - /// This operation is not supported for a stack. - protected override void OnRemoveAt(int index) - { - throw new NotSupportedException(); - } + /// + /// Throws a as this operation is not supported for a stack. + /// + /// The index of the item to remove. + /// This operation is not supported for a stack. + protected override void OnRemoveAt(int index) + { + throw new NotSupportedException(); + } - /// - /// Throws a as this operation is not supported for a stack. - /// - /// The item to locate in the stack. - /// This method always throws an exception. - /// This operation is not supported for a stack. - protected override int OnIndexOf(T item) - { - throw new NotSupportedException(); - } + /// + /// Throws a as this operation is not supported for a stack. + /// + /// The item to locate in the stack. + /// This method always throws an exception. + /// This operation is not supported for a stack. + protected override int OnIndexOf(T item) + { + throw new NotSupportedException(); } } \ No newline at end of file diff --git a/Common/AllocationArray.cs b/Common/AllocationArray.cs index c77658f8..7d354c18 100644 --- a/Common/AllocationArray.cs +++ b/Common/AllocationArray.cs @@ -1,242 +1,241 @@ -namespace Ecng.Common -{ - using System; - using System.Collections; - using System.Collections.Generic; +namespace Ecng.Common; + +using System; +using System.Collections; +using System.Collections.Generic; +/// +/// Represents a dynamically resizing array allocation. +/// +/// The type of elements stored in the array. +public class AllocationArray : IEnumerable +{ /// - /// Represents a dynamically resizing array allocation. + /// Enumerator for the AllocationArray. /// - /// The type of elements stored in the array. - public class AllocationArray : IEnumerable + private class AllocationArrayEnumerator(AllocationArray parent) : IEnumerator { + private readonly AllocationArray _parent = parent ?? throw new ArgumentNullException(nameof(parent)); + private T _current; + private int _pos; + /// - /// Enumerator for the AllocationArray. + /// Releases resources used by the enumerator. /// - private class AllocationArrayEnumerator(AllocationArray parent) : IEnumerator + void IDisposable.Dispose() => _pos = 0; + + /// + /// Advances the enumerator to the next element. + /// + /// true if the enumerator was successfully advanced; otherwise, false. + bool IEnumerator.MoveNext() { - private readonly AllocationArray _parent = parent ?? throw new ArgumentNullException(nameof(parent)); - private T _current; - private int _pos; - - /// - /// Releases resources used by the enumerator. - /// - void IDisposable.Dispose() => _pos = 0; - - /// - /// Advances the enumerator to the next element. - /// - /// true if the enumerator was successfully advanced; otherwise, false. - bool IEnumerator.MoveNext() + if (_pos < _parent._count) { - if (_pos < _parent._count) - { - _current = _parent._buffer[_pos++]; - return true; - } - - return false; + _current = _parent._buffer[_pos++]; + return true; } - /// - /// Resets the enumerator to its initial position. - /// - void IEnumerator.Reset() => _pos = 0; - - /// - /// Gets the current element in the array. - /// - T IEnumerator.Current => _current; - - /// - /// Gets the current element in the array. - /// - object IEnumerator.Current => _current; + return false; } - //private readonly int _capacity; - private T[] _buffer; - private readonly AllocationArrayEnumerator _enumerator; - /// - /// Initializes a new instance of the class with the specified capacity. + /// Resets the enumerator to its initial position. /// - /// The initial capacity of the array. Must be at least 1. - public AllocationArray(int capacity = 1) - { - if (capacity < 1) - throw new ArgumentOutOfRangeException(nameof(capacity)); - - //_capacity = capacity; - _buffer = new T[capacity]; - _enumerator = new AllocationArrayEnumerator(this); - } + void IEnumerator.Reset() => _pos = 0; /// - /// Gets or sets the maximum allowed count of elements. + /// Gets the current element in the array. /// - public int MaxCount { get; set; } = int.MaxValue / 4; - - private int _count; + T IEnumerator.Current => _current; /// - /// Gets or sets the current number of elements in the array. + /// Gets the current element in the array. /// - public int Count - { - get => _count; - set - { - if (_buffer.Length < value) - { - if (value > MaxCount) - throw new ArgumentOutOfRangeException(); + object IEnumerator.Current => _current; + } - Resize(value); - } + //private readonly int _capacity; + private T[] _buffer; + private readonly AllocationArrayEnumerator _enumerator; - _count = value; - } - } + /// + /// Initializes a new instance of the class with the specified capacity. + /// + /// The initial capacity of the array. Must be at least 1. + public AllocationArray(int capacity = 1) + { + if (capacity < 1) + throw new ArgumentOutOfRangeException(nameof(capacity)); - /// - /// Gets or sets the element at the specified index. - /// - /// The zero-based index of the element to get or set. - /// The element at the specified index. - public T this[int index] + //_capacity = capacity; + _buffer = new T[capacity]; + _enumerator = new AllocationArrayEnumerator(this); + } + + /// + /// Gets or sets the maximum allowed count of elements. + /// + public int MaxCount { get; set; } = int.MaxValue / 4; + + private int _count; + + /// + /// Gets or sets the current number of elements in the array. + /// + public int Count + { + get => _count; + set { - get + if (_buffer.Length < value) { - if (index >= Count) + if (value > MaxCount) throw new ArgumentOutOfRangeException(); - return _buffer[index]; + Resize(value); } - set - { - if (index >= Count) - Count = index + 1; - _buffer[index] = value; - } + _count = value; } + } - /// - /// Gets the underlying buffer array. - /// - public T[] Buffer => _buffer; - - /// - /// Resets the array, clearing all elements and optionally resizing the buffer. - /// - /// The desired capacity. Must be at least 1. - public void Reset(int capacity = 1) + /// + /// Gets or sets the element at the specified index. + /// + /// The zero-based index of the element to get or set. + /// The element at the specified index. + public T this[int index] + { + get { - if (capacity < 1) - throw new ArgumentOutOfRangeException(nameof(capacity)); + if (index >= Count) + throw new ArgumentOutOfRangeException(); - _count = 0; + return _buffer[index]; + } + set + { + if (index >= Count) + Count = index + 1; - if (_buffer.Length < capacity) - Resize(capacity); + _buffer[index] = value; } + } - /// - /// Ensures that the array has the specified new size capacity. - /// - /// The required size. - private void EnsureCapacity(int newSize) - { - if (_buffer.Length > newSize) - return; + /// + /// Gets the underlying buffer array. + /// + public T[] Buffer => _buffer; - if (newSize > MaxCount) - throw new ArgumentOutOfRangeException(); + /// + /// Resets the array, clearing all elements and optionally resizing the buffer. + /// + /// The desired capacity. Must be at least 1. + public void Reset(int capacity = 1) + { + if (capacity < 1) + throw new ArgumentOutOfRangeException(nameof(capacity)); - Resize(newSize * 2); - } + _count = 0; - /// - /// Adds an item to the end of the array. - /// - /// The item to add. - public void Add(T item) - { - EnsureCapacity(_count + 1); + if (_buffer.Length < capacity) + Resize(capacity); + } - _buffer[_count] = item; - _count++; - } + /// + /// Ensures that the array has the specified new size capacity. + /// + /// The required size. + private void EnsureCapacity(int newSize) + { + if (_buffer.Length > newSize) + return; - /// - /// Adds a range of items from an array to the end of the allocation array. - /// - /// The array of items to add. - /// The zero-based index at which to start copying from the source array. - /// The number of items to copy. - public void Add(T[] items, int offset, int count) - { - EnsureCapacity(_count + count); + if (newSize > MaxCount) + throw new ArgumentOutOfRangeException(); - Array.Copy(items, offset, _buffer, _count, count); - _count += count; - } + Resize(newSize * 2); + } - /// - /// Removes the item at the specified index. - /// - /// The zero-based index of the item to remove. - public void RemoveAt(int index) - { - RemoveRange(index, 1); - } + /// + /// Adds an item to the end of the array. + /// + /// The item to add. + public void Add(T item) + { + EnsureCapacity(_count + 1); - /// - /// Removes a range of items from the array. - /// - /// The zero-based starting index of the range to remove. - /// The number of items to remove. - public void RemoveRange(int startIndex, int count) - { - if (startIndex < 0) - throw new ArgumentOutOfRangeException(nameof(startIndex)); + _buffer[_count] = item; + _count++; + } - if (count <= 0) - throw new ArgumentOutOfRangeException(nameof(count)); + /// + /// Adds a range of items from an array to the end of the allocation array. + /// + /// The array of items to add. + /// The zero-based index at which to start copying from the source array. + /// The number of items to copy. + public void Add(T[] items, int offset, int count) + { + EnsureCapacity(_count + count); - var begin = startIndex + count; - var countOfMove = _count - begin; + Array.Copy(items, offset, _buffer, _count, count); + _count += count; + } - if (countOfMove < 0) - throw new ArgumentOutOfRangeException(nameof(count)); + /// + /// Removes the item at the specified index. + /// + /// The zero-based index of the item to remove. + public void RemoveAt(int index) + { + RemoveRange(index, 1); + } - if (countOfMove > 0) - Array.Copy(_buffer, begin, _buffer, startIndex, countOfMove); + /// + /// Removes a range of items from the array. + /// + /// The zero-based starting index of the range to remove. + /// The number of items to remove. + public void RemoveRange(int startIndex, int count) + { + if (startIndex < 0) + throw new ArgumentOutOfRangeException(nameof(startIndex)); - _count -= count; - } + if (count <= 0) + throw new ArgumentOutOfRangeException(nameof(count)); - /// - /// Returns an enumerator that iterates through the collection. - /// - /// An enumerator for the allocation array. - public IEnumerator GetEnumerator() => _enumerator; + var begin = startIndex + count; + var countOfMove = _count - begin; - /// - /// Returns an enumerator that iterates through the collection. - /// - /// An enumerator for the allocation array. - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + if (countOfMove < 0) + throw new ArgumentOutOfRangeException(nameof(count)); - /// - /// Resizes the buffer array to the specified capacity. - /// - /// The new capacity of the buffer. - private void Resize(int capacity) - { - Array.Resize(ref _buffer, capacity); - } + if (countOfMove > 0) + Array.Copy(_buffer, begin, _buffer, startIndex, countOfMove); + + _count -= count; + } + + /// + /// Returns an enumerator that iterates through the collection. + /// + /// An enumerator for the allocation array. + public IEnumerator GetEnumerator() => _enumerator; + + /// + /// Returns an enumerator that iterates through the collection. + /// + /// An enumerator for the allocation array. + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + /// Resizes the buffer array to the specified capacity. + /// + /// The new capacity of the buffer. + private void Resize(int capacity) + { + Array.Resize(ref _buffer, capacity); } } \ No newline at end of file diff --git a/Common/ArrayHelper.cs b/Common/ArrayHelper.cs index 6c4875f8..34665f2f 100644 --- a/Common/ArrayHelper.cs +++ b/Common/ArrayHelper.cs @@ -1,185 +1,184 @@ -namespace Ecng.Common +namespace Ecng.Common; + +using System; +using System.Collections.Generic; + +/// +/// Utility class for common array operations. +/// +public static class ArrayHelper { - using System; - using System.Collections.Generic; + /// + /// Sets all of the elements in the specified to zero, false, or null, depending on the element type. + /// + /// The array whose elements need to be cleared. + /// Thrown when the array is null. + public static void Clear(this Array array) + { + if (array is null) + throw new ArgumentNullException(nameof(array)); + + Clear(array, 0, array.Length); + } + + /// + /// Sets a range of elements in the specified to zero, false, or null, depending on the element type. + /// + /// The array whose elements need to be cleared. + /// The starting index of the range of elements to clear. + /// The number of elements to clear. + /// Thrown when the array is null. + public static void Clear(this Array array, int index, int count) + { + Array.Clear(array, index, count); + } /// - /// Utility class for common array operations. + /// Returns a subarray that starts at the specified index and extends to the end of the array. /// - public static class ArrayHelper + /// The type of elements in the array. + /// The source array. + /// The starting index of the subarray. + /// A new array containing the elements from the specified index to the end of the source array. + /// Thrown when the array is null. + public static T[] Range(this T[] array, int index) { - /// - /// Sets all of the elements in the specified to zero, false, or null, depending on the element type. - /// - /// The array whose elements need to be cleared. - /// Thrown when the array is null. - public static void Clear(this Array array) - { - if (array is null) - throw new ArgumentNullException(nameof(array)); - - Clear(array, 0, array.Length); - } - - /// - /// Sets a range of elements in the specified to zero, false, or null, depending on the element type. - /// - /// The array whose elements need to be cleared. - /// The starting index of the range of elements to clear. - /// The number of elements to clear. - /// Thrown when the array is null. - public static void Clear(this Array array, int index, int count) - { - Array.Clear(array, index, count); - } - - /// - /// Returns a subarray that starts at the specified index and extends to the end of the array. - /// - /// The type of elements in the array. - /// The source array. - /// The starting index of the subarray. - /// A new array containing the elements from the specified index to the end of the source array. - /// Thrown when the array is null. - public static T[] Range(this T[] array, int index) - { - if (array is null) - throw new ArgumentNullException(nameof(array)); - - return array.Range(index, array.Length - index); - } - - /// - /// Returns a subarray that begins at the specified index and contains the specified number of elements. - /// - /// The type of elements in the array. - /// The source array. - /// The starting index of the subarray. - /// The number of elements to include in the subarray. - /// A new array containing the specified range of elements from the source array. - public static T[] Range(this T[] array, int index, int count) - { - var range = new T[count]; - Array.Copy(array, index, range, 0, count); - return range; - } - - /// - /// Creates a one-dimensional of the specified and length, with zero-based indexing. - /// - /// The type of the array to create. - /// The size of the array to create. - /// A new array of the specified type and length. - public static Array CreateArray(this Type type, int count) - { - return Array.CreateInstance(type, count); - } - - /// - /// Searches for the specified object and returns the index of the first occurrence within the entire one-dimensional array. - /// - /// The type of elements in the array. - /// The one-dimensional array to search. - /// The object to locate in the array. - /// - /// The index of the first occurrence of value within the entire array, if found; otherwise, the lower bound of the array minus 1. - /// - public static int IndexOf(this T[] array, T item) - { - return Array.IndexOf(array, item); - } - - /// - /// Creates a shallow copy of the specified array. - /// - /// The type of elements in the array. - /// The source array. - /// A new array that is a shallow copy of the source array. - /// Thrown when the array is null. - public static T[] Clone(this T[] array) - { - if (array is null) - throw new ArgumentNullException(nameof(array)); - - return (T[])array.Clone(); - } - - /// - /// Returns a reversed copy of the specified array. - /// - /// The type of elements in the array. - /// The source array. - /// A new array with the elements in reverse order. - /// Thrown when the array is null. - public static T[] Reverse(this T[] array) - { - if (array is null) - throw new ArgumentNullException(nameof(array)); - - var clone = (T[])array.Clone(); - Array.Reverse(clone); - return clone; - } - - /// - /// Concatenates two arrays into a single new array. - /// - /// The type of elements in the arrays. - /// The first array to concatenate. - /// The second array to concatenate. - /// A new array that contains the elements of the first array followed by the elements of the second array. - /// Thrown when either of the arrays is null. - public static T[] Concat(this T[] first, T[] second) - { - if (first is null) - throw new ArgumentNullException(nameof(first)); - - if (second is null) - throw new ArgumentNullException(nameof(second)); - - var result = new T[first.Length + second.Length]; - - if (result.Length == 0) - return result; - - Array.Copy(first, result, first.Length); - Array.Copy(second, 0, result, first.Length, second.Length); + if (array is null) + throw new ArgumentNullException(nameof(array)); + return array.Range(index, array.Length - index); + } + + /// + /// Returns a subarray that begins at the specified index and contains the specified number of elements. + /// + /// The type of elements in the array. + /// The source array. + /// The starting index of the subarray. + /// The number of elements to include in the subarray. + /// A new array containing the specified range of elements from the source array. + public static T[] Range(this T[] array, int index, int count) + { + var range = new T[count]; + Array.Copy(array, index, range, 0, count); + return range; + } + + /// + /// Creates a one-dimensional of the specified and length, with zero-based indexing. + /// + /// The type of the array to create. + /// The size of the array to create. + /// A new array of the specified type and length. + public static Array CreateArray(this Type type, int count) + { + return Array.CreateInstance(type, count); + } + + /// + /// Searches for the specified object and returns the index of the first occurrence within the entire one-dimensional array. + /// + /// The type of elements in the array. + /// The one-dimensional array to search. + /// The object to locate in the array. + /// + /// The index of the first occurrence of value within the entire array, if found; otherwise, the lower bound of the array minus 1. + /// + public static int IndexOf(this T[] array, T item) + { + return Array.IndexOf(array, item); + } + + /// + /// Creates a shallow copy of the specified array. + /// + /// The type of elements in the array. + /// The source array. + /// A new array that is a shallow copy of the source array. + /// Thrown when the array is null. + public static T[] Clone(this T[] array) + { + if (array is null) + throw new ArgumentNullException(nameof(array)); + + return (T[])array.Clone(); + } + + /// + /// Returns a reversed copy of the specified array. + /// + /// The type of elements in the array. + /// The source array. + /// A new array with the elements in reverse order. + /// Thrown when the array is null. + public static T[] Reverse(this T[] array) + { + if (array is null) + throw new ArgumentNullException(nameof(array)); + + var clone = (T[])array.Clone(); + Array.Reverse(clone); + return clone; + } + + /// + /// Concatenates two arrays into a single new array. + /// + /// The type of elements in the arrays. + /// The first array to concatenate. + /// The second array to concatenate. + /// A new array that contains the elements of the first array followed by the elements of the second array. + /// Thrown when either of the arrays is null. + public static T[] Concat(this T[] first, T[] second) + { + if (first is null) + throw new ArgumentNullException(nameof(first)); + + if (second is null) + throw new ArgumentNullException(nameof(second)); + + var result = new T[first.Length + second.Length]; + + if (result.Length == 0) return result; - } - - /// - /// Creates a copy of the source array. - /// - /// The type of elements in the array. - /// The source array. - /// A new array containing the same elements as the source array. - /// Thrown when the source array is null. - public static T[] CopyArray(this T[] source) - { - if (source is null) - throw new ArgumentNullException(nameof(source)); - - var copy = new T[source.Length]; - source.CopyTo(copy, 0); - return copy; - } - - /// - /// Creates a copy of the elements from the specified collection into a new array. - /// - /// The type of elements in the collection. - /// The source collection. - /// A new array containing the elements of the source collection. - /// Thrown when the source collection is null. - public static T[] CopyArray(this ICollection source) - { - if (source is null) - throw new ArgumentNullException(nameof(source)); - - var copy = new T[source.Count]; - source.CopyTo(copy, 0); - return copy; - } + + Array.Copy(first, result, first.Length); + Array.Copy(second, 0, result, first.Length, second.Length); + + return result; + } + + /// + /// Creates a copy of the source array. + /// + /// The type of elements in the array. + /// The source array. + /// A new array containing the same elements as the source array. + /// Thrown when the source array is null. + public static T[] CopyArray(this T[] source) + { + if (source is null) + throw new ArgumentNullException(nameof(source)); + + var copy = new T[source.Length]; + source.CopyTo(copy, 0); + return copy; + } + + /// + /// Creates a copy of the elements from the specified collection into a new array. + /// + /// The type of elements in the collection. + /// The source collection. + /// A new array containing the elements of the source collection. + /// Thrown when the source collection is null. + public static T[] CopyArray(this ICollection source) + { + if (source is null) + throw new ArgumentNullException(nameof(source)); + + var copy = new T[source.Count]; + source.CopyTo(copy, 0); + return copy; } } \ No newline at end of file diff --git a/Common/AttributeHelper.cs b/Common/AttributeHelper.cs index 46ab5f29..bae5b843 100644 --- a/Common/AttributeHelper.cs +++ b/Common/AttributeHelper.cs @@ -1,134 +1,133 @@ -namespace Ecng.Common +namespace Ecng.Common; + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Reflection; + +/// +/// Provides helper methods for working with custom attributes including caching support. +/// +public static class AttributeHelper { - using System; - using System.Collections.Generic; - using System.ComponentModel; - using System.Linq; - using System.Reflection; + private static readonly Dictionary<(Type, ICustomAttributeProvider), Attribute> _attrCache = new(); /// - /// Provides helper methods for working with custom attributes including caching support. + /// Gets or sets a value indicating whether attribute caching is enabled. /// - public static class AttributeHelper + public static bool CacheEnabled { get; set; } = true; + + /// + /// Clears the internal attribute cache. + /// + public static void ClearCache() => _attrCache.Clear(); + + /// + /// Retrieves the first custom attribute of the specified type, optionally searching the ancestors. + /// If caching is enabled, the attribute is stored and reused. + /// + /// The type of the custom attribute to retrieve. + /// The attribute provider to search. + /// true to inspect the ancestors of the provider; otherwise, false. + /// + /// The first attribute of type found on the provider; otherwise, null. + /// + public static TAttribute GetAttribute(this ICustomAttributeProvider provider, bool inherit = true) + where TAttribute : Attribute { - private static readonly Dictionary<(Type, ICustomAttributeProvider), Attribute> _attrCache = new(); - - /// - /// Gets or sets a value indicating whether attribute caching is enabled. - /// - public static bool CacheEnabled { get; set; } = true; - - /// - /// Clears the internal attribute cache. - /// - public static void ClearCache() => _attrCache.Clear(); - - /// - /// Retrieves the first custom attribute of the specified type, optionally searching the ancestors. - /// If caching is enabled, the attribute is stored and reused. - /// - /// The type of the custom attribute to retrieve. - /// The attribute provider to search. - /// true to inspect the ancestors of the provider; otherwise, false. - /// - /// The first attribute of type found on the provider; otherwise, null. - /// - public static TAttribute GetAttribute(this ICustomAttributeProvider provider, bool inherit = true) - where TAttribute : Attribute - { - if (provider is null) - throw new ArgumentNullException(nameof(provider)); + if (provider is null) + throw new ArgumentNullException(nameof(provider)); - TAttribute GetAttribute() - => provider.GetCustomAttributes(typeof(TAttribute), inherit).Cast().FirstOrDefault(); + TAttribute GetAttribute() + => provider.GetCustomAttributes(typeof(TAttribute), inherit).Cast().FirstOrDefault(); - if (!CacheEnabled) - return GetAttribute(); + if (!CacheEnabled) + return GetAttribute(); - return (TAttribute)_attrCache.SafeAdd(new(typeof(TAttribute), provider), - key => GetAttribute()); - } + return (TAttribute)_attrCache.SafeAdd(new(typeof(TAttribute), provider), + key => GetAttribute()); + } - /// - /// Retrieves all custom attributes of the specified type from the provider, optionally searching the ancestors. - /// - /// The type of the custom attributes to retrieve. - /// The attribute provider to search. - /// true to inspect the ancestors of the provider; otherwise, false. - /// An enumerable collection of attributes of type . - public static IEnumerable GetAttributes(this ICustomAttributeProvider provider, bool inherit = true) - where TAttribute : Attribute - { - if (provider is null) - throw new ArgumentNullException(nameof(provider)); + /// + /// Retrieves all custom attributes of the specified type from the provider, optionally searching the ancestors. + /// + /// The type of the custom attributes to retrieve. + /// The attribute provider to search. + /// true to inspect the ancestors of the provider; otherwise, false. + /// An enumerable collection of attributes of type . + public static IEnumerable GetAttributes(this ICustomAttributeProvider provider, bool inherit = true) + where TAttribute : Attribute + { + if (provider is null) + throw new ArgumentNullException(nameof(provider)); - return provider.GetCustomAttributes(typeof(TAttribute), inherit).Cast(); - } + return provider.GetCustomAttributes(typeof(TAttribute), inherit).Cast(); + } - /// - /// Retrieves all custom attributes from the provider, optionally searching the ancestors. - /// - /// The attribute provider to search. - /// true to inspect the ancestors of the provider; otherwise, false. - /// An enumerable collection of custom attributes. - public static IEnumerable GetAttributes(this ICustomAttributeProvider provider, bool inherit = true) - { - if (provider is null) - throw new ArgumentNullException(nameof(provider)); + /// + /// Retrieves all custom attributes from the provider, optionally searching the ancestors. + /// + /// The attribute provider to search. + /// true to inspect the ancestors of the provider; otherwise, false. + /// An enumerable collection of custom attributes. + public static IEnumerable GetAttributes(this ICustomAttributeProvider provider, bool inherit = true) + { + if (provider is null) + throw new ArgumentNullException(nameof(provider)); - return provider.GetCustomAttributes(inherit).Cast(); - } + return provider.GetCustomAttributes(inherit).Cast(); + } - // Note: This private method does not require XML documentation. - private static TValue SafeAdd(this IDictionary dictionary, TKey key, Func handler) - { - if (dictionary is null) - throw new ArgumentNullException(nameof(dictionary)); + // Note: This private method does not require XML documentation. + private static TValue SafeAdd(this IDictionary dictionary, TKey key, Func handler) + { + if (dictionary is null) + throw new ArgumentNullException(nameof(dictionary)); - if (handler is null) - throw new ArgumentNullException(nameof(handler)); + if (handler is null) + throw new ArgumentNullException(nameof(handler)); - if (!dictionary.TryGetValue(key, out var value)) + if (!dictionary.TryGetValue(key, out var value)) + { + lock (dictionary) { - lock (dictionary) + if (!dictionary.TryGetValue(key, out value)) { - if (!dictionary.TryGetValue(key, out value)) - { - value = handler(key); - dictionary.Add(key, value); - } + value = handler(key); + dictionary.Add(key, value); } } - - return value; } - /// - /// Determines whether the provider is marked with the . - /// - /// The attribute provider to check. - /// true if the provider has the ; otherwise, false. - public static bool IsObsolete(this ICustomAttributeProvider provider) - { - if (provider is null) - throw new ArgumentNullException(nameof(provider)); + return value; + } - return provider.GetAttribute() != null; - } + /// + /// Determines whether the provider is marked with the . + /// + /// The attribute provider to check. + /// true if the provider has the ; otherwise, false. + public static bool IsObsolete(this ICustomAttributeProvider provider) + { + if (provider is null) + throw new ArgumentNullException(nameof(provider)); - /// - /// Determines whether the provider is marked as browsable via the . - /// - /// The attribute provider to check. - /// - /// true if the is not present or its property is true; otherwise, false. - /// - public static bool IsBrowsable(this ICustomAttributeProvider provider) - { - if (provider is null) - throw new ArgumentNullException(nameof(provider)); + return provider.GetAttribute() != null; + } - return provider.GetAttribute()?.Browsable != false; - } + /// + /// Determines whether the provider is marked as browsable via the . + /// + /// The attribute provider to check. + /// + /// true if the is not present or its property is true; otherwise, false. + /// + public static bool IsBrowsable(this ICustomAttributeProvider provider) + { + if (provider is null) + throw new ArgumentNullException(nameof(provider)); + + return provider.GetAttribute()?.Browsable != false; } } \ No newline at end of file diff --git a/Common/CloneHelper.cs b/Common/CloneHelper.cs index 8739baf7..0e42015a 100644 --- a/Common/CloneHelper.cs +++ b/Common/CloneHelper.cs @@ -1,27 +1,26 @@ -namespace Ecng.Common -{ - using System; +namespace Ecng.Common; + +using System; +/// +/// Provides helper methods for performing cloning operations on objects that implement the ICloneable interface. +/// +public static class CloneHelper +{ /// - /// Provides helper methods for performing cloning operations on objects that implement the ICloneable interface. + /// Creates a clone of the specified object. /// - public static class CloneHelper + /// A type that implements ICloneable. + /// The object to clone. + /// A cloned instance of the object. + public static T TypedClone(this T value) + where T : ICloneable { - /// - /// Creates a clone of the specified object. - /// - /// A type that implements ICloneable. - /// The object to clone. - /// A cloned instance of the object. - public static T TypedClone(this T value) - where T : ICloneable - { - return (T)value.Clone(); - } - - /// - /// Represents a marker helper class indicating that UI-related parts should be excluded during cloning. - /// - public class CloneWithoutUI {} + return (T)value.Clone(); } + + /// + /// Represents a marker helper class indicating that UI-related parts should be excluded during cloning. + /// + public class CloneWithoutUI {} } diff --git a/Common/Cloneable.cs b/Common/Cloneable.cs index 8c69543d..384c685c 100644 --- a/Common/Cloneable.cs +++ b/Common/Cloneable.cs @@ -1,40 +1,35 @@ -namespace Ecng.Common -{ - #region Using Directives - - using System; +namespace Ecng.Common; - #endregion +using System; +/// +/// The base class for objects that can be cloned. +/// +/// The type of cloned object. +[Serializable] +public abstract class Cloneable : ICloneable + //where T : Cloneable +{ /// - /// The base class for objects that can be cloned. + /// Creates a new object that is a copy of the current instance. /// - /// The type of cloned object. - [Serializable] - public abstract class Cloneable : ICloneable - //where T : Cloneable - { - /// - /// Creates a new object that is a copy of the current instance. - /// - /// - /// A new object that is a copy of this instance. - /// - public abstract T Clone(); - - #region ICloneable Members + /// + /// A new object that is a copy of this instance. + /// + public abstract T Clone(); - /// - /// Creates a new object that is a copy of the current instance. - /// - /// - /// A new object that is a copy of this instance. - /// - object ICloneable.Clone() - { - return Clone(); - } + #region ICloneable Members - #endregion + /// + /// Creates a new object that is a copy of the current instance. + /// + /// + /// A new object that is a copy of this instance. + /// + object ICloneable.Clone() + { + return Clone(); } + + #endregion } \ No newline at end of file diff --git a/Common/CompareHelper.cs b/Common/CompareHelper.cs index 1ef28add..69ff02f0 100644 --- a/Common/CompareHelper.cs +++ b/Common/CompareHelper.cs @@ -1,177 +1,176 @@ -namespace Ecng.Common +namespace Ecng.Common; + +using System; +using System.Collections.Generic; +using System.Net; + +/// +/// Provides helper methods for comparing various types including IPAddress, Type, object, and Version. +/// +public static class CompareHelper { - using System; - using System.Collections.Generic; - using System.Net; + /// + /// Compares two IPAddress instances by converting them to a numeric value. + /// + /// The first IPAddress to compare. + /// The second IPAddress to compare. + /// + /// A signed integer that indicates the relative values of the two IP addresses. + /// Less than zero if first is less than second, zero if equal, and greater than zero if first is greater than second. + /// + public static int Compare(this IPAddress first, IPAddress second) + { + return first.To().CompareTo(second.To()); + } /// - /// Provides helper methods for comparing various types including IPAddress, Type, object, and Version. + /// Compares two Type instances for equality with an option to consider inheritance. /// - public static class CompareHelper + /// The first Type to compare. + /// The second Type to compare. + /// If set to true, uses inheritance to determine equality. + /// + /// True if the types are considered equal; otherwise, false. + /// + /// Thrown if either or is null. + public static bool Compare(this Type first, Type second, bool useInheritance) { - /// - /// Compares two IPAddress instances by converting them to a numeric value. - /// - /// The first IPAddress to compare. - /// The second IPAddress to compare. - /// - /// A signed integer that indicates the relative values of the two IP addresses. - /// Less than zero if first is less than second, zero if equal, and greater than zero if first is greater than second. - /// - public static int Compare(this IPAddress first, IPAddress second) - { - return first.To().CompareTo(second.To()); - } + if (first is null) + throw new ArgumentNullException(nameof(first)); - /// - /// Compares two Type instances for equality with an option to consider inheritance. - /// - /// The first Type to compare. - /// The second Type to compare. - /// If set to true, uses inheritance to determine equality. - /// - /// True if the types are considered equal; otherwise, false. - /// - /// Thrown if either or is null. - public static bool Compare(this Type first, Type second, bool useInheritance) - { - if (first is null) - throw new ArgumentNullException(nameof(first)); + if (second is null) + throw new ArgumentNullException(nameof(second)); - if (second is null) - throw new ArgumentNullException(nameof(second)); + if (useInheritance) + return second.Is(first); + else + return first == second; + } - if (useInheritance) - return second.Is(first); - else - return first == second; - } + /// + /// Compares two Type instances with inheritance ordering. + /// + /// The first Type to compare. + /// The second Type to compare. + /// + /// 0 if the types are identical; 1 if is assignable from ; otherwise, -1. + /// + /// Thrown if either or is null. + public static int Compare(this Type first, Type second) + { + if (first is null) + throw new ArgumentNullException(nameof(first)); + + if (second is null) + throw new ArgumentNullException(nameof(second)); + + if (first == second) + return 0; + else if (second.Is(first)) + return 1; + else + return -1; + } - /// - /// Compares two Type instances with inheritance ordering. - /// - /// The first Type to compare. - /// The second Type to compare. - /// - /// 0 if the types are identical; 1 if is assignable from ; otherwise, -1. - /// - /// Thrown if either or is null. - public static int Compare(this Type first, Type second) - { - if (first is null) - throw new ArgumentNullException(nameof(first)); + /// + /// Compares two objects of the same type that implement . + /// + /// The first object to compare. + /// The second object to compare. + /// + /// A signed integer that indicates the relative values of the two objects. + /// Less than zero if is less than , zero if equal, and greater than zero if greater. + /// + /// + /// Thrown if the objects are not of the same type or if they do not implement . + /// + public static int Compare(this object value1, object value2) + { + if (value1 is null && value2 is null) + return 0; - if (second is null) - throw new ArgumentNullException(nameof(second)); + if (value1 is null) + return -1; - if (first == second) - return 0; - else if (second.Is(first)) - return 1; - else - return -1; - } + if (value2 is null) + return 1; - /// - /// Compares two objects of the same type that implement . - /// - /// The first object to compare. - /// The second object to compare. - /// - /// A signed integer that indicates the relative values of the two objects. - /// Less than zero if is less than , zero if equal, and greater than zero if greater. - /// - /// - /// Thrown if the objects are not of the same type or if they do not implement . - /// - public static int Compare(this object value1, object value2) - { - if (value1 is null && value2 is null) - return 0; + if (value1.GetType() != value2.GetType()) + throw new ArgumentException("The values must be a same types.", nameof(value2)); - if (value1 is null) - return -1; - if (value2 is null) - return 1; - - if (value1.GetType() != value2.GetType()) - throw new ArgumentException("The values must be a same types.", nameof(value2)); + if (value1 is IComparable compare1) + return compare1.CompareTo(value2); + throw new ArgumentException("The values must be IComparable."); + } - if (value1 is IComparable compare1) - return compare1.CompareTo(value2); + /// + /// Determines whether a value is equal to its default value. + /// + /// The type of the value. + /// The value to check. + /// + /// True if the value equals the default value of its type; otherwise, false. + /// + public static bool IsDefault(this T value) + { + return EqualityComparer.Default.Equals(value, default); + } - throw new ArgumentException("The values must be IComparable."); - } + /// + /// Determines whether a value is equal to the default value as computed at runtime. + /// + /// The type of the value. + /// The value to check. + /// + /// True if the value equals the runtime default value; otherwise, false. + /// + public static bool IsRuntimeDefault(this T value) + { + return EqualityComparer.Default.Equals(value, (T)value.GetType().GetDefaultValue()); + } - /// - /// Determines whether a value is equal to its default value. - /// - /// The type of the value. - /// The value to check. - /// - /// True if the value equals the default value of its type; otherwise, false. - /// - public static bool IsDefault(this T value) + /// + /// Compares two Version instances. + /// + /// The first Version to compare. + /// The second Version to compare. + /// + /// A signed integer that indicates the relative values of the two versions. + /// Less than zero if is less than , zero if equal, and greater than zero if greater. + /// + public static int Compare(this Version first, Version second) + { + if (first is null) { - return EqualityComparer.Default.Equals(value, default); - } + if (second is null) + return 0; - /// - /// Determines whether a value is equal to the default value as computed at runtime. - /// - /// The type of the value. - /// The value to check. - /// - /// True if the value equals the runtime default value; otherwise, false. - /// - public static bool IsRuntimeDefault(this T value) - { - return EqualityComparer.Default.Equals(value, (T)value.GetType().GetDefaultValue()); + return -1; } - /// - /// Compares two Version instances. - /// - /// The first Version to compare. - /// The second Version to compare. - /// - /// A signed integer that indicates the relative values of the two versions. - /// Less than zero if is less than , zero if equal, and greater than zero if greater. - /// - public static int Compare(this Version first, Version second) - { - if (first is null) - { - if (second is null) - return 0; - - return -1; - } - - if (second is null) - return 1; + if (second is null) + return 1; - var firstBuild = first.Build != -1 ? first.Build : 0; - var firstRevision = first.Revision != -1 ? first.Revision : 0; + var firstBuild = first.Build != -1 ? first.Build : 0; + var firstRevision = first.Revision != -1 ? first.Revision : 0; - var secondBuild = second.Build != -1 ? second.Build : 0; - var secondRevision = second.Revision != -1 ? second.Revision : 0; + var secondBuild = second.Build != -1 ? second.Build : 0; + var secondRevision = second.Revision != -1 ? second.Revision : 0; - if (first.Major != second.Major) - return first.Major > second.Major ? 1 : -1; - - if (first.Minor != second.Minor) - return first.Minor > second.Minor ? 1 : -1; + if (first.Major != second.Major) + return first.Major > second.Major ? 1 : -1; + + if (first.Minor != second.Minor) + return first.Minor > second.Minor ? 1 : -1; - if (firstBuild != secondBuild) - return firstBuild > secondBuild ? 1 : -1; + if (firstBuild != secondBuild) + return firstBuild > secondBuild ? 1 : -1; - if (firstRevision == secondRevision) - return 0; + if (firstRevision == secondRevision) + return 0; - return firstRevision > secondRevision ? 1 : -1; - } + return firstRevision > secondRevision ? 1 : -1; } } \ No newline at end of file diff --git a/Common/ComparisonOperator.cs b/Common/ComparisonOperator.cs index 4e7cf4f1..72ad8110 100644 --- a/Common/ComparisonOperator.cs +++ b/Common/ComparisonOperator.cs @@ -1,58 +1,57 @@ -namespace Ecng.Common +namespace Ecng.Common; + +using System.ComponentModel.DataAnnotations; + +/// +/// Represents the method for comparing two parameter values. +/// +public enum ComparisonOperator { - using System.ComponentModel.DataAnnotations; - - /// - /// Represents the method for comparing two parameter values. - /// - public enum ComparisonOperator - { - /// - /// Indicates that the parameter values are equal. - /// - [Display(Name = "=")] - Equal, - - /// - /// Indicates that the parameter values are not equal. - /// - [Display(Name = "!=")] - NotEqual, - - /// - /// Indicates that the left parameter value is strictly greater than the right parameter value. - /// - [Display(Name = ">")] - Greater, - - /// - /// Indicates that the left parameter value is greater than or equal to the right parameter value. - /// - [Display(Name = ">=")] - GreaterOrEqual, - - /// - /// Indicates that the left parameter value is strictly less than the right parameter value. - /// - [Display(Name = "<")] - Less, - - /// - /// Indicates that the left parameter value is less than or equal to the right parameter value. - /// - [Display(Name = "<=")] - LessOrEqual, - - /// - /// Indicates that the left parameter value can be any value. - /// - [Display(Name = "Any")] - Any, - - /// - /// Indicates that the left parameter value is contained within the right parameter value. - /// - [Display(Name = "IN")] - In, - } + /// + /// Indicates that the parameter values are equal. + /// + [Display(Name = "=")] + Equal, + + /// + /// Indicates that the parameter values are not equal. + /// + [Display(Name = "!=")] + NotEqual, + + /// + /// Indicates that the left parameter value is strictly greater than the right parameter value. + /// + [Display(Name = ">")] + Greater, + + /// + /// Indicates that the left parameter value is greater than or equal to the right parameter value. + /// + [Display(Name = ">=")] + GreaterOrEqual, + + /// + /// Indicates that the left parameter value is strictly less than the right parameter value. + /// + [Display(Name = "<")] + Less, + + /// + /// Indicates that the left parameter value is less than or equal to the right parameter value. + /// + [Display(Name = "<=")] + LessOrEqual, + + /// + /// Indicates that the left parameter value can be any value. + /// + [Display(Name = "Any")] + Any, + + /// + /// Indicates that the left parameter value is contained within the right parameter value. + /// + [Display(Name = "IN")] + In, } diff --git a/Common/ConsoleHelper.cs b/Common/ConsoleHelper.cs index 6ea77ea6..a0da02b3 100644 --- a/Common/ConsoleHelper.cs +++ b/Common/ConsoleHelper.cs @@ -1,136 +1,135 @@ -namespace Ecng.Common +namespace Ecng.Common; + +using System; +using System.Security; + +/// +/// Provides helper methods for writing colored messages to the console and reading secure passwords. +/// +public static class ConsoleHelper { - using System; - using System.Security; + /// + /// Gets or sets the color used to display information messages. + /// + public static ConsoleColor Info = ConsoleColor.White; + + /// + /// Gets or sets the color used to display warning messages. + /// + public static ConsoleColor Warning = ConsoleColor.Yellow; /// - /// Provides helper methods for writing colored messages to the console and reading secure passwords. + /// Gets or sets the color used to display error messages. /// - public static class ConsoleHelper + public static ConsoleColor Error = ConsoleColor.Red; + + /// + /// Gets or sets the color used to display success messages. + /// + public static ConsoleColor Success = ConsoleColor.Green; + + /// + /// Writes an information message to the console. + /// + /// The message to write. + public static void ConsoleInfo(this string message) { - /// - /// Gets or sets the color used to display information messages. - /// - public static ConsoleColor Info = ConsoleColor.White; - - /// - /// Gets or sets the color used to display warning messages. - /// - public static ConsoleColor Warning = ConsoleColor.Yellow; - - /// - /// Gets or sets the color used to display error messages. - /// - public static ConsoleColor Error = ConsoleColor.Red; - - /// - /// Gets or sets the color used to display success messages. - /// - public static ConsoleColor Success = ConsoleColor.Green; - - /// - /// Writes an information message to the console. - /// - /// The message to write. - public static void ConsoleInfo(this string message) - { - Console.WriteLine(message); - } + Console.WriteLine(message); + } - /// - /// Writes a warning message to the console using the predefined warning color. - /// - /// The warning message to write. - public static void ConsoleWarning(this string message) - { - message.ConsoleWithColor(Warning); - } + /// + /// Writes a warning message to the console using the predefined warning color. + /// + /// The warning message to write. + public static void ConsoleWarning(this string message) + { + message.ConsoleWithColor(Warning); + } - /// - /// Writes an error message to the console using the predefined error color. - /// - /// The error message to write. - public static void ConsoleError(this string message) - { - message.ConsoleWithColor(Error); - } + /// + /// Writes an error message to the console using the predefined error color. + /// + /// The error message to write. + public static void ConsoleError(this string message) + { + message.ConsoleWithColor(Error); + } - /// - /// Writes a success message to the console using the predefined success color. - /// - /// The success message to write. - public static void ConsoleSuccess(this string message) - { - message.ConsoleWithColor(Success); - } + /// + /// Writes a success message to the console using the predefined success color. + /// + /// The success message to write. + public static void ConsoleSuccess(this string message) + { + message.ConsoleWithColor(Success); + } - /// - /// Writes a message to the console with the specified color. - /// - /// The message to write. - /// The color to use for the message. - public static void ConsoleWithColor(this string message, ConsoleColor color) - { - ConsoleWithColor(() => Console.WriteLine(message), color); - } + /// + /// Writes a message to the console with the specified color. + /// + /// The message to write. + /// The color to use for the message. + public static void ConsoleWithColor(this string message, ConsoleColor color) + { + ConsoleWithColor(() => Console.WriteLine(message), color); + } + + private static readonly SyncObject _lock = new(); - private static readonly SyncObject _lock = new(); + /// + /// Executes the provided action while displaying console output in the specified color. + /// + /// The action to execute. + /// The color to use for the console output. + /// Thrown when the handler is null. + public static void ConsoleWithColor(this Action handler, ConsoleColor color) + { + if (handler is null) + throw new ArgumentNullException(nameof(handler)); - /// - /// Executes the provided action while displaying console output in the specified color. - /// - /// The action to execute. - /// The color to use for the console output. - /// Thrown when the handler is null. - public static void ConsoleWithColor(this Action handler, ConsoleColor color) + lock (_lock) { - if (handler is null) - throw new ArgumentNullException(nameof(handler)); + var prevColor = Console.ForegroundColor; - lock (_lock) + Console.ForegroundColor = color; + + try { - var prevColor = Console.ForegroundColor; - - Console.ForegroundColor = color; - - try - { - handler(); - } - finally - { - Console.ForegroundColor = prevColor; - } + handler(); + } + finally + { + Console.ForegroundColor = prevColor; } } + } + + /// + /// Reads a password from the console without displaying it, outputting a masked version. + /// + /// A that contains the password. + public static SecureString ReadPassword() + { + var pass = new SecureString(); + ConsoleKeyInfo key; - /// - /// Reads a password from the console without displaying it, outputting a masked version. - /// - /// A that contains the password. - public static SecureString ReadPassword() + do { - var pass = new SecureString(); - ConsoleKeyInfo key; + key = Console.ReadKey(true); - do + if (!char.IsControl(key.KeyChar)) { - key = Console.ReadKey(true); - - if (!char.IsControl(key.KeyChar)) - { - pass.AppendChar(key.KeyChar); - Console.Write("*"); - } - else if (key.Key == ConsoleKey.Backspace && pass.Length > 0) - { - pass.RemoveAt(pass.Length - 1); - Console.Write("\b \b"); - } + pass.AppendChar(key.KeyChar); + Console.Write("*"); + } + else if (key.Key == ConsoleKey.Backspace && pass.Length > 0) + { + pass.RemoveAt(pass.Length - 1); + Console.Write("\b \b"); } - while (key.Key != ConsoleKey.Enter); - - return pass; } + while (key.Key != ConsoleKey.Enter); + + return pass; } } \ No newline at end of file diff --git a/Common/Converter.cs b/Common/Converter.cs index 5ed562db..8953824e 100644 --- a/Common/Converter.cs +++ b/Common/Converter.cs @@ -1,1151 +1,1150 @@ -namespace Ecng.Common +namespace Ecng.Common; + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Data.Common; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Reflection; +using System.Security; +using System.Text; +using System.Linq; +using System.Runtime.InteropServices; +using System.Globalization; +using System.Threading; +using System.Xml; +using System.Xml.Linq; + +using TimeZoneConverter; + +/// +/// The converter class. +/// +public static class Converter { - using System; - using System.Collections; - using System.Collections.Generic; - using System.ComponentModel; - using System.Data; - using System.Data.Common; - using System.IO; - using System.Net; - using System.Net.Sockets; - using System.Reflection; - using System.Security; - using System.Text; - using System.Linq; - using System.Runtime.InteropServices; - using System.Globalization; - using System.Threading; - using System.Xml; - using System.Xml.Linq; - - using TimeZoneConverter; + private static readonly Dictionary _dbTypes = []; + private static readonly Dictionary _sharpAliases = []; + private static readonly Dictionary _sharpAliasesByValue = []; + private static readonly Dictionary _typeCache = []; - /// - /// The converter class. - /// - public static class Converter - { - private static readonly Dictionary _dbTypes = []; - private static readonly Dictionary _sharpAliases = []; - private static readonly Dictionary _sharpAliasesByValue = []; - private static readonly Dictionary _typeCache = []; - - private static readonly Dictionary<(Type, Type), Delegate> _typedConverters = []; - private static readonly Dictionary<(Type, Type), Func> _typedConverters2 = []; + private static readonly Dictionary<(Type, Type), Delegate> _typedConverters = []; + private static readonly Dictionary<(Type, Type), Func> _typedConverters2 = []; - static Converter() + static Converter() + { + _dbTypes.Add(typeof(string), DbType.String); + _dbTypes.Add(typeof(char), DbType.String); + _dbTypes.Add(typeof(short), DbType.Int16); + _dbTypes.Add(typeof(int), DbType.Int32); + _dbTypes.Add(typeof(long), DbType.Int64); + _dbTypes.Add(typeof(ushort), DbType.UInt16); + _dbTypes.Add(typeof(uint), DbType.UInt32); + _dbTypes.Add(typeof(ulong), DbType.UInt64); + _dbTypes.Add(typeof(float), DbType.Single); + _dbTypes.Add(typeof(double), DbType.Double); + _dbTypes.Add(typeof(decimal), DbType.Decimal); + _dbTypes.Add(typeof(DateTime), DbType.DateTime); + _dbTypes.Add(typeof(DateTimeOffset), DbType.DateTimeOffset); + _dbTypes.Add(typeof(TimeSpan), DbType.Time); + _dbTypes.Add(typeof(Guid), DbType.Guid); + _dbTypes.Add(typeof(byte[]), DbType.Binary); + _dbTypes.Add(typeof(byte), DbType.Byte); + _dbTypes.Add(typeof(sbyte), DbType.SByte); + _dbTypes.Add(typeof(bool), DbType.Boolean); + _dbTypes.Add(typeof(object), DbType.Object); + + AddCSharpAlias(typeof(object), "object"); + AddCSharpAlias(typeof(bool), "bool"); + AddCSharpAlias(typeof(byte), "byte"); + AddCSharpAlias(typeof(sbyte), "sbyte"); + AddCSharpAlias(typeof(char), "char"); + AddCSharpAlias(typeof(decimal), "decimal"); + AddCSharpAlias(typeof(double), "double"); + AddCSharpAlias(typeof(float), "float"); + AddCSharpAlias(typeof(int), "int"); + AddCSharpAlias(typeof(uint), "uint"); + AddCSharpAlias(typeof(long), "long"); + AddCSharpAlias(typeof(ulong), "ulong"); + AddCSharpAlias(typeof(short), "short"); + AddCSharpAlias(typeof(ushort), "ushort"); + AddCSharpAlias(typeof(string), "string"); + AddCSharpAlias(typeof(void), "void"); + + AddTypedConverter(input => { - _dbTypes.Add(typeof(string), DbType.String); - _dbTypes.Add(typeof(char), DbType.String); - _dbTypes.Add(typeof(short), DbType.Int16); - _dbTypes.Add(typeof(int), DbType.Int32); - _dbTypes.Add(typeof(long), DbType.Int64); - _dbTypes.Add(typeof(ushort), DbType.UInt16); - _dbTypes.Add(typeof(uint), DbType.UInt32); - _dbTypes.Add(typeof(ulong), DbType.UInt64); - _dbTypes.Add(typeof(float), DbType.Single); - _dbTypes.Add(typeof(double), DbType.Double); - _dbTypes.Add(typeof(decimal), DbType.Decimal); - _dbTypes.Add(typeof(DateTime), DbType.DateTime); - _dbTypes.Add(typeof(DateTimeOffset), DbType.DateTimeOffset); - _dbTypes.Add(typeof(TimeSpan), DbType.Time); - _dbTypes.Add(typeof(Guid), DbType.Guid); - _dbTypes.Add(typeof(byte[]), DbType.Binary); - _dbTypes.Add(typeof(byte), DbType.Byte); - _dbTypes.Add(typeof(sbyte), DbType.SByte); - _dbTypes.Add(typeof(bool), DbType.Boolean); - _dbTypes.Add(typeof(object), DbType.Object); - - AddCSharpAlias(typeof(object), "object"); - AddCSharpAlias(typeof(bool), "bool"); - AddCSharpAlias(typeof(byte), "byte"); - AddCSharpAlias(typeof(sbyte), "sbyte"); - AddCSharpAlias(typeof(char), "char"); - AddCSharpAlias(typeof(decimal), "decimal"); - AddCSharpAlias(typeof(double), "double"); - AddCSharpAlias(typeof(float), "float"); - AddCSharpAlias(typeof(int), "int"); - AddCSharpAlias(typeof(uint), "uint"); - AddCSharpAlias(typeof(long), "long"); - AddCSharpAlias(typeof(ulong), "ulong"); - AddCSharpAlias(typeof(short), "short"); - AddCSharpAlias(typeof(ushort), "ushort"); - AddCSharpAlias(typeof(string), "string"); - AddCSharpAlias(typeof(void), "void"); - - AddTypedConverter(input => - { - if (input.IsNullable()) - input = input.GetGenericArguments()[0]; + if (input.IsNullable()) + input = input.GetGenericArguments()[0]; - if (input.IsEnum()) - input = input.GetEnumBaseType(); + if (input.IsEnum()) + input = input.GetEnumBaseType(); - if (_dbTypes.TryGetValue(input, out var dbType)) - return dbType; - else - throw new ArgumentException($".NET type {input} doesn't have associated db type."); - }); - - AddTypedConverter(input => _dbTypes.First(pair => pair.Value == input).Key); - AddTypedConverter(input => input.Unicode()); - AddTypedConverter(input => input.Unicode()); - AddTypedConverter(input => new BitArray(input)); - AddTypedConverter(input => - { - var source = new bool[input.Length]; - input.CopyTo(source, 0); - return source; - }); - AddTypedConverter(input => new BitArray(input)); - AddTypedConverter(input => - { - var source = new byte[(int)((double)input.Length / 8).Ceiling()]; - input.CopyTo(source, 0); - return source; - }); - AddTypedConverter(input => input.ToString()); - AddTypedConverter(s => s.IsEmpty() ? null : IPAddress.Parse(s)); - AddTypedConverter(input => input.GetAddressBytes()); - AddTypedConverter(input => new IPAddress(input)); - AddTypedConverter(input => + if (_dbTypes.TryGetValue(input, out var dbType)) + return dbType; + else + throw new ArgumentException($".NET type {input} doesn't have associated db type."); + }); + + AddTypedConverter(input => _dbTypes.First(pair => pair.Value == input).Key); + AddTypedConverter(input => input.Unicode()); + AddTypedConverter(input => input.Unicode()); + AddTypedConverter(input => new BitArray(input)); + AddTypedConverter(input => + { + var source = new bool[input.Length]; + input.CopyTo(source, 0); + return source; + }); + AddTypedConverter(input => new BitArray(input)); + AddTypedConverter(input => + { + var source = new byte[(int)((double)input.Length / 8).Ceiling()]; + input.CopyTo(source, 0); + return source; + }); + AddTypedConverter(input => input.ToString()); + AddTypedConverter(s => s.IsEmpty() ? null : IPAddress.Parse(s)); + AddTypedConverter(input => input.GetAddressBytes()); + AddTypedConverter(input => new IPAddress(input)); + AddTypedConverter(input => + { + switch (input.AddressFamily) { - switch (input.AddressFamily) + case AddressFamily.InterNetworkV6: { - case AddressFamily.InterNetworkV6: - { - return input.ScopeId; - } - case AddressFamily.InterNetwork: - { - var byteIp = input.GetAddressBytes(); - return ((((byteIp[3] << 0x18) | (byteIp[2] << 0x10)) | (byteIp[1] << 8)) | byteIp[0]) & (0xffffffff); - //retVal = BitConverter.ToInt32(addr.GetAddressBytes(), 0); - } - default: - throw new ArgumentException("Can't convert IPAddress to long.", nameof(input)); + return input.ScopeId; } - }); - AddTypedConverter(input => new IPAddress(input)); - AddTypedConverter(input => (IPEndPoint)input.TypedTo()); - AddTypedConverter(input => (DnsEndPoint)input.TypedTo()); - AddTypedConverter(input => - { - var index = input.LastIndexOf(':'); - - if (index != -1) + case AddressFamily.InterNetwork: { - var host = input.Substring(0, index); + var byteIp = input.GetAddressBytes(); + return ((((byteIp[3] << 0x18) | (byteIp[2] << 0x10)) | (byteIp[1] << 8)) | byteIp[0]) & (0xffffffff); + //retVal = BitConverter.ToInt32(addr.GetAddressBytes(), 0); + } + default: + throw new ArgumentException("Can't convert IPAddress to long.", nameof(input)); + } + }); + AddTypedConverter(input => new IPAddress(input)); + AddTypedConverter(input => (IPEndPoint)input.TypedTo()); + AddTypedConverter(input => (DnsEndPoint)input.TypedTo()); + AddTypedConverter(input => + { + var index = input.LastIndexOf(':'); - var portStr = input.Substring(index + 1); - if (portStr.Length > 0 && portStr.Last() == '/') - portStr = portStr.Substring(0, portStr.Length - 1); + if (index != -1) + { + var host = input.Substring(0, index); - var port = portStr.To(); + var portStr = input.Substring(index + 1); + if (portStr.Length > 0 && portStr.Last() == '/') + portStr = portStr.Substring(0, portStr.Length - 1); - if (!IPAddress.TryParse(host, out var addr)) - return new DnsEndPoint(host, port); + var port = portStr.To(); - return new IPEndPoint(addr, port); - } - else - { - if (input.IsEmpty()) - return null; + if (!IPAddress.TryParse(host, out var addr)) + return new DnsEndPoint(host, port); - throw new FormatException("Invalid endpoint format."); - } - }); - AddTypedConverter(input => input.GetHost() + ":" + input.GetPort()); - AddTypedConverter(input => + return new IPEndPoint(addr, port); + } + else { - var key = input.ToLowerInvariant(); + if (input.IsEmpty()) + return null; - if (_sharpAliases.TryGetValue(key, out var type)) - return type; + throw new FormatException("Invalid endpoint format."); + } + }); + AddTypedConverter(input => input.GetHost() + ":" + input.GetPort()); + AddTypedConverter(input => + { + var key = input.ToLowerInvariant(); + + if (_sharpAliases.TryGetValue(key, out var type)) + return type; + + if (_typeCache.TryGetValue(key, out type)) + return type; + lock (_typeCache) + { if (_typeCache.TryGetValue(key, out type)) return type; - lock (_typeCache) - { - if (_typeCache.TryGetValue(key, out type)) - return type; - - type = Type.GetType(input, false, true); + type = Type.GetType(input, false, true); - // в строке может быть записаное не AssemblyQualifiedName, а только полное имя типа + имя сборки. - if (type is null) + // в строке может быть записаное не AssemblyQualifiedName, а только полное имя типа + имя сборки. + if (type is null) + { + var parts = input.SplitBySep(", "); + if (parts.Length == 2 || parts.Length == 5) { - var parts = input.SplitBySep(", "); - if (parts.Length == 2 || parts.Length == 5) + var asm = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().Name == parts[1]); + + if (asm is null) { - var asm = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().Name == parts[1]); - - if (asm is null) + try { - try - { - asm = Assembly.Load(parts[1]); - } - catch (FileNotFoundException) - { - } + asm = Assembly.Load(parts[1]); } - - if (asm != null) + catch (FileNotFoundException) { - type = asm.GetType(parts[0]); } + } + + if (asm != null) + { + type = asm.GetType(parts[0]); + } + + if (type is null) + { + var asmName = parts[1].Trim(); - if (type is null) + if ( + asmName.EqualsIgnoreCase("System.Private.CoreLib") || + asmName.EqualsIgnoreCase("mscorlib") + ) { - var asmName = parts[1].Trim(); - - if ( - asmName.EqualsIgnoreCase("System.Private.CoreLib") || - asmName.EqualsIgnoreCase("mscorlib") - ) - { - asm = typeof(object).Assembly; - type = asm.GetType(parts[0]); - } + asm = typeof(object).Assembly; + type = asm.GetType(parts[0]); } } } - - if (type != null) - _typeCache.Add(key, type); - else - throw new ArgumentException($"Type {input} doesn't exists.", nameof(input)); } - return type; - }); - AddTypedConverter(input => input.AssemblyQualifiedName); - AddTypedConverter(input => input.ToString()); - AddTypedConverter(input => new StringBuilder(input)); - AddTypedConverter(input => input.ToString()); - AddTypedConverter(input => new DbConnectionStringBuilder { ConnectionString = input }); - AddTypedConverter(input => input.UnSecure()); - AddTypedConverter(input => input.Secure()); - AddTypedConverter(input => + if (type != null) + _typeCache.Add(key, type); + else + throw new ArgumentException($"Type {input} doesn't exists.", nameof(input)); + } + + return type; + }); + AddTypedConverter(input => input.AssemblyQualifiedName); + AddTypedConverter(input => input.ToString()); + AddTypedConverter(input => new StringBuilder(input)); + AddTypedConverter(input => input.ToString()); + AddTypedConverter(input => new DbConnectionStringBuilder { ConnectionString = input }); + AddTypedConverter(input => input.UnSecure()); + AddTypedConverter(input => input.Secure()); + AddTypedConverter(input => + { + unsafe static SecureString Secure(char[] input) { - unsafe static SecureString Secure(char[] input) - { - if (input.Length == 0) - return new(); + if (input.Length == 0) + return new(); - fixed (char* p = input) - return new(p, input.Length); - } + fixed (char* p = input) + return new(p, input.Length); + } - return Secure(input); - }); - AddTypedConverter(input => - { - var charArray = new char[input.Length / 2]; - - var offset = 0; - for (var i = 0; i < input.Length; i += 2) - charArray[offset++] = BitConverter.ToChar([input[i], input[i + 1]], 0); - - return charArray.TypedTo(); - }); - AddTypedConverter(input => input.ToCharArray()); - AddTypedConverter(input => new string(input)); - AddTypedConverter(input => [input]); - AddTypedConverter(input => input[0]); - AddTypedConverter(BitConverter.GetBytes); - AddTypedConverter(input => BitConverter.ToBoolean(input, 0)); - AddTypedConverter(BitConverter.GetBytes); - AddTypedConverter(input => BitConverter.ToChar(input, 0)); - AddTypedConverter(BitConverter.GetBytes); - AddTypedConverter(input => BitConverter.ToInt16(input, 0)); - AddTypedConverter(BitConverter.GetBytes); - AddTypedConverter(input => BitConverter.ToInt32(input, 0)); - AddTypedConverter(BitConverter.GetBytes); - AddTypedConverter(input => BitConverter.ToInt64(input, 0)); - AddTypedConverter(BitConverter.GetBytes); - AddTypedConverter(input => BitConverter.ToUInt16(input, 0)); - AddTypedConverter(BitConverter.GetBytes); - AddTypedConverter(input => BitConverter.ToUInt32(input, 0)); - AddTypedConverter(BitConverter.GetBytes); - AddTypedConverter(input => BitConverter.ToUInt64(input, 0)); - AddTypedConverter(BitConverter.GetBytes); - AddTypedConverter(input => BitConverter.ToSingle(input, 0)); - AddTypedConverter(BitConverter.GetBytes); - AddTypedConverter(input => BitConverter.ToDouble(input, 0)); - AddTypedConverter(input => BitConverter.GetBytes(input.Ticks)); - AddTypedConverter(input => new DateTime(BitConverter.ToInt64(input, 0))); - AddTypedConverter(input => BitConverter.GetBytes(input.UtcTicks)); - AddTypedConverter(input => new DateTimeOffset(BitConverter.ToInt64(input, 0), TimeSpan.Zero)); - AddTypedConverter(input => BitConverter.GetBytes(input.Ticks)); - AddTypedConverter(input => new TimeSpan(BitConverter.ToInt64(input, 0))); - AddTypedConverter(input => input.ToByteArray()); - AddTypedConverter(input => new Guid(input)); - - AddTypedConverter(input => - { - var bits = decimal.GetBits(input); - - var lo = bits[0]; - var mid = bits[1]; - var hi = bits[2]; - var flags = bits[3]; - - var bytes = new byte[16]; - - bytes[0] = (byte)lo; - bytes[1] = (byte)(lo >> 8); - bytes[2] = (byte)(lo >> 0x10); - bytes[3] = (byte)(lo >> 0x18); - bytes[4] = (byte)mid; - bytes[5] = (byte)(mid >> 8); - bytes[6] = (byte)(mid >> 0x10); - bytes[7] = (byte)(mid >> 0x18); - bytes[8] = (byte)hi; - bytes[9] = (byte)(hi >> 8); - bytes[10] = (byte)(hi >> 0x10); - bytes[11] = (byte)(hi >> 0x18); - bytes[12] = (byte)flags; - bytes[13] = (byte)(flags >> 8); - bytes[14] = (byte)(flags >> 0x10); - bytes[15] = (byte)(flags >> 0x18); - - return bytes; - }); - AddTypedConverter(input => - { - var bytes = input; + return Secure(input); + }); + AddTypedConverter(input => + { + var charArray = new char[input.Length / 2]; + + var offset = 0; + for (var i = 0; i < input.Length; i += 2) + charArray[offset++] = BitConverter.ToChar([input[i], input[i + 1]], 0); + + return charArray.TypedTo(); + }); + AddTypedConverter(input => input.ToCharArray()); + AddTypedConverter(input => new string(input)); + AddTypedConverter(input => [input]); + AddTypedConverter(input => input[0]); + AddTypedConverter(BitConverter.GetBytes); + AddTypedConverter(input => BitConverter.ToBoolean(input, 0)); + AddTypedConverter(BitConverter.GetBytes); + AddTypedConverter(input => BitConverter.ToChar(input, 0)); + AddTypedConverter(BitConverter.GetBytes); + AddTypedConverter(input => BitConverter.ToInt16(input, 0)); + AddTypedConverter(BitConverter.GetBytes); + AddTypedConverter(input => BitConverter.ToInt32(input, 0)); + AddTypedConverter(BitConverter.GetBytes); + AddTypedConverter(input => BitConverter.ToInt64(input, 0)); + AddTypedConverter(BitConverter.GetBytes); + AddTypedConverter(input => BitConverter.ToUInt16(input, 0)); + AddTypedConverter(BitConverter.GetBytes); + AddTypedConverter(input => BitConverter.ToUInt32(input, 0)); + AddTypedConverter(BitConverter.GetBytes); + AddTypedConverter(input => BitConverter.ToUInt64(input, 0)); + AddTypedConverter(BitConverter.GetBytes); + AddTypedConverter(input => BitConverter.ToSingle(input, 0)); + AddTypedConverter(BitConverter.GetBytes); + AddTypedConverter(input => BitConverter.ToDouble(input, 0)); + AddTypedConverter(input => BitConverter.GetBytes(input.Ticks)); + AddTypedConverter(input => new DateTime(BitConverter.ToInt64(input, 0))); + AddTypedConverter(input => BitConverter.GetBytes(input.UtcTicks)); + AddTypedConverter(input => new DateTimeOffset(BitConverter.ToInt64(input, 0), TimeSpan.Zero)); + AddTypedConverter(input => BitConverter.GetBytes(input.Ticks)); + AddTypedConverter(input => new TimeSpan(BitConverter.ToInt64(input, 0))); + AddTypedConverter(input => input.ToByteArray()); + AddTypedConverter(input => new Guid(input)); + + AddTypedConverter(input => + { + var bits = decimal.GetBits(input); + + var lo = bits[0]; + var mid = bits[1]; + var hi = bits[2]; + var flags = bits[3]; + + var bytes = new byte[16]; + + bytes[0] = (byte)lo; + bytes[1] = (byte)(lo >> 8); + bytes[2] = (byte)(lo >> 0x10); + bytes[3] = (byte)(lo >> 0x18); + bytes[4] = (byte)mid; + bytes[5] = (byte)(mid >> 8); + bytes[6] = (byte)(mid >> 0x10); + bytes[7] = (byte)(mid >> 0x18); + bytes[8] = (byte)hi; + bytes[9] = (byte)(hi >> 8); + bytes[10] = (byte)(hi >> 0x10); + bytes[11] = (byte)(hi >> 0x18); + bytes[12] = (byte)flags; + bytes[13] = (byte)(flags >> 8); + bytes[14] = (byte)(flags >> 0x10); + bytes[15] = (byte)(flags >> 0x18); - var bits = new[] - { - ((bytes[0] | (bytes[1] << 8)) | (bytes[2] << 0x10)) | (bytes[3] << 0x18), //lo - ((bytes[4] | (bytes[5] << 8)) | (bytes[6] << 0x10)) | (bytes[7] << 0x18), //mid - ((bytes[8] | (bytes[9] << 8)) | (bytes[10] << 0x10)) | (bytes[11] << 0x18), //hi - ((bytes[12] | (bytes[13] << 8)) | (bytes[14] << 0x10)) | (bytes[15] << 0x18) //flags - }; + return bytes; + }); + AddTypedConverter(input => + { + var bytes = input; - return new decimal(bits); - }); + var bits = new[] + { + ((bytes[0] | (bytes[1] << 8)) | (bytes[2] << 0x10)) | (bytes[3] << 0x18), //lo + ((bytes[4] | (bytes[5] << 8)) | (bytes[6] << 0x10)) | (bytes[7] << 0x18), //mid + ((bytes[8] | (bytes[9] << 8)) | (bytes[10] << 0x10)) | (bytes[11] << 0x18), //hi + ((bytes[12] | (bytes[13] << 8)) | (bytes[14] << 0x10)) | (bytes[15] << 0x18) //flags + }; - AddTypedConverter(input => new decimal(input)); - AddTypedConverter(decimal.GetBits); + return new decimal(bits); + }); - AddTypedConverter(input => input.Ticks); - AddTypedConverter(input => new TimeSpan(input)); - AddTypedConverter(input => input.Ticks); - AddTypedConverter(input => new DateTime(input)); - AddTypedConverter(input => input.UtcTicks); - AddTypedConverter(input => new DateTimeOffset(input, TimeSpan.Zero)); + AddTypedConverter(input => new decimal(input)); + AddTypedConverter(decimal.GetBits); - AddTypedConverter(input => input.ToOADate()); - AddTypedConverter(DateTime.FromOADate); + AddTypedConverter(input => input.Ticks); + AddTypedConverter(input => new TimeSpan(input)); + AddTypedConverter(input => input.Ticks); + AddTypedConverter(input => new DateTime(input)); + AddTypedConverter(input => input.UtcTicks); + AddTypedConverter(input => new DateTimeOffset(input, TimeSpan.Zero)); - AddTypedConverter(input => - { - if (input == DateTime.MinValue) - return DateTimeOffset.MinValue; - else if (input == DateTime.MaxValue) - return DateTimeOffset.MaxValue; - else - return new DateTimeOffset(input); - }); - AddTypedConverter(input => - { - if (input == DateTimeOffset.MinValue) - return DateTime.MinValue; - else if (input == DateTimeOffset.MaxValue) - return DateTime.MaxValue; - else - return input.UtcDateTime; - }); - - AddTypedConverter(input => input.ToString()); - AddTypedConverter(byte.Parse); - AddTypedConverter(input => input.ToString()); - AddTypedConverter(sbyte.Parse); - AddTypedConverter(input => input.ToString()); - AddTypedConverter(input => - { - if (input == "1") - return true; - else if (input == "0") - return false; - else - return bool.Parse(input); - }); - AddTypedConverter(s => s.IsEmpty() ? null : s.To()); - AddTypedConverter(input => input.ToString()); - AddTypedConverter(float.Parse); - AddTypedConverter(s => s.IsEmpty() ? null : float.Parse(s)); - AddTypedConverter(input => input.ToString()); - AddTypedConverter(double.Parse); - AddTypedConverter(s => s.IsEmpty() ? null : double.Parse(s)); - AddTypedConverter(input => input.ToString()); - AddTypedConverter(decimal.Parse); - AddTypedConverter(s => s.IsEmpty() ? null : decimal.Parse(s)); - AddTypedConverter(input => input.ToString()); - AddTypedConverter(short.Parse); - AddTypedConverter(s => s.IsEmpty() ? null : short.Parse(s)); - AddTypedConverter(input => input.ToString()); - AddTypedConverter(int.Parse); - AddTypedConverter(s => s.IsEmpty() ? null : int.Parse(s)); - AddTypedConverter(input => input.ToString()); - AddTypedConverter(long.Parse); - AddTypedConverter(s => s.IsEmpty() ? null : long.Parse(s)); - AddTypedConverter(input => input.ToString()); - AddTypedConverter(ushort.Parse); - AddTypedConverter(s => s.IsEmpty() ? null : ushort.Parse(s)); - AddTypedConverter(input => input.ToString()); - AddTypedConverter(uint.Parse); - AddTypedConverter(s => s.IsEmpty() ? null : uint.Parse(s)); - AddTypedConverter(input => input.ToString()); - AddTypedConverter(ulong.Parse); - AddTypedConverter(s => s.IsEmpty() ? null : ulong.Parse(s)); - AddTypedConverter(input => input.ToString()); - AddTypedConverter(char.Parse); - AddTypedConverter(input => input.ToString()); - AddTypedConverter(OSPlatform.Create); - AddTypedConverter(input => input.Name); - AddTypedConverter(CultureInfo.GetCultureInfo); - AddTypedConverter(input => input.ToString()); - AddTypedConverter(input => new(input)); - AddTypedConverter(input => input.ToString()); - AddTypedConverter(input => new(input)); - AddTypedConverter(input => input.CodePage); - AddTypedConverter(input => Encoding.GetEncoding(input)); - AddTypedConverter(input => input.LCID); - AddTypedConverter(input => new(input)); - AddTypedConverter(input => input.ToInt32()); - AddTypedConverter(input => new(input)); - AddTypedConverter(input => input.ToInt64()); - AddTypedConverter(input => new(input)); - AddTypedConverter(input => input.ToUInt32()); - AddTypedConverter(input => new(input)); - AddTypedConverter(input => input.ToUInt64()); - AddTypedConverter(input => new(input)); - AddTypedConverter(input => input.ToString()); - AddTypedConverter(TimeSpan.Parse); - AddTypedConverter(s => s.IsEmpty() ? null : TimeSpan.Parse(s)); - AddTypedConverter(input => input.ToString()); - AddTypedConverter(Guid.Parse); - AddTypedConverter(s => s.IsEmpty() ? null : Guid.Parse(s)); - AddTypedConverter(input => input.Id); - AddTypedConverter(s => TZConvert.TryGetTimeZoneInfo(s, out var tz) ? tz : TimeZoneInfo.Utc); - } + AddTypedConverter(input => input.ToOADate()); + AddTypedConverter(DateTime.FromOADate); - /// - /// Adds a typed converter function for converting from TFrom to TTo type. - /// - /// The source type to convert from. - /// The destination type to convert to. - /// The converter function to add. - /// Thrown when converter is null. - public static void AddTypedConverter(Func converter) + AddTypedConverter(input => + { + if (input == DateTime.MinValue) + return DateTimeOffset.MinValue; + else if (input == DateTime.MaxValue) + return DateTimeOffset.MaxValue; + else + return new DateTimeOffset(input); + }); + AddTypedConverter(input => + { + if (input == DateTimeOffset.MinValue) + return DateTime.MinValue; + else if (input == DateTimeOffset.MaxValue) + return DateTime.MaxValue; + else + return input.UtcDateTime; + }); + + AddTypedConverter(input => input.ToString()); + AddTypedConverter(byte.Parse); + AddTypedConverter(input => input.ToString()); + AddTypedConverter(sbyte.Parse); + AddTypedConverter(input => input.ToString()); + AddTypedConverter(input => { - if (converter is null) - throw new ArgumentNullException(nameof(converter)); + if (input == "1") + return true; + else if (input == "0") + return false; + else + return bool.Parse(input); + }); + AddTypedConverter(s => s.IsEmpty() ? null : s.To()); + AddTypedConverter(input => input.ToString()); + AddTypedConverter(float.Parse); + AddTypedConverter(s => s.IsEmpty() ? null : float.Parse(s)); + AddTypedConverter(input => input.ToString()); + AddTypedConverter(double.Parse); + AddTypedConverter(s => s.IsEmpty() ? null : double.Parse(s)); + AddTypedConverter(input => input.ToString()); + AddTypedConverter(decimal.Parse); + AddTypedConverter(s => s.IsEmpty() ? null : decimal.Parse(s)); + AddTypedConverter(input => input.ToString()); + AddTypedConverter(short.Parse); + AddTypedConverter(s => s.IsEmpty() ? null : short.Parse(s)); + AddTypedConverter(input => input.ToString()); + AddTypedConverter(int.Parse); + AddTypedConverter(s => s.IsEmpty() ? null : int.Parse(s)); + AddTypedConverter(input => input.ToString()); + AddTypedConverter(long.Parse); + AddTypedConverter(s => s.IsEmpty() ? null : long.Parse(s)); + AddTypedConverter(input => input.ToString()); + AddTypedConverter(ushort.Parse); + AddTypedConverter(s => s.IsEmpty() ? null : ushort.Parse(s)); + AddTypedConverter(input => input.ToString()); + AddTypedConverter(uint.Parse); + AddTypedConverter(s => s.IsEmpty() ? null : uint.Parse(s)); + AddTypedConverter(input => input.ToString()); + AddTypedConverter(ulong.Parse); + AddTypedConverter(s => s.IsEmpty() ? null : ulong.Parse(s)); + AddTypedConverter(input => input.ToString()); + AddTypedConverter(char.Parse); + AddTypedConverter(input => input.ToString()); + AddTypedConverter(OSPlatform.Create); + AddTypedConverter(input => input.Name); + AddTypedConverter(CultureInfo.GetCultureInfo); + AddTypedConverter(input => input.ToString()); + AddTypedConverter(input => new(input)); + AddTypedConverter(input => input.ToString()); + AddTypedConverter(input => new(input)); + AddTypedConverter(input => input.CodePage); + AddTypedConverter(input => Encoding.GetEncoding(input)); + AddTypedConverter(input => input.LCID); + AddTypedConverter(input => new(input)); + AddTypedConverter(input => input.ToInt32()); + AddTypedConverter(input => new(input)); + AddTypedConverter(input => input.ToInt64()); + AddTypedConverter(input => new(input)); + AddTypedConverter(input => input.ToUInt32()); + AddTypedConverter(input => new(input)); + AddTypedConverter(input => input.ToUInt64()); + AddTypedConverter(input => new(input)); + AddTypedConverter(input => input.ToString()); + AddTypedConverter(TimeSpan.Parse); + AddTypedConverter(s => s.IsEmpty() ? null : TimeSpan.Parse(s)); + AddTypedConverter(input => input.ToString()); + AddTypedConverter(Guid.Parse); + AddTypedConverter(s => s.IsEmpty() ? null : Guid.Parse(s)); + AddTypedConverter(input => input.Id); + AddTypedConverter(s => TZConvert.TryGetTimeZoneInfo(s, out var tz) ? tz : TimeZoneInfo.Utc); + } - var key = (typeof(TFrom), typeof(TTo)); + /// + /// Adds a typed converter function for converting from TFrom to TTo type. + /// + /// The source type to convert from. + /// The destination type to convert to. + /// The converter function to add. + /// Thrown when converter is null. + public static void AddTypedConverter(Func converter) + { + if (converter is null) + throw new ArgumentNullException(nameof(converter)); - _typedConverters.Add(key, converter); - _typedConverters2.Add(key, input => converter((TFrom)input)); - } + var key = (typeof(TFrom), typeof(TTo)); - /// - /// Adds a typed converter function for converting between specified types. - /// - /// Tuple containing source and destination types. - /// The converter function to add. - /// Thrown when converter is null. - public static void AddTypedConverter((Type, Type) key, Func converter) - { - if (converter is null) - throw new ArgumentNullException(nameof(converter)); + _typedConverters.Add(key, converter); + _typedConverters2.Add(key, input => converter((TFrom)input)); + } - _typedConverters.Add(key, converter); - _typedConverters2.Add(key, converter); - } + /// + /// Adds a typed converter function for converting between specified types. + /// + /// Tuple containing source and destination types. + /// The converter function to add. + /// Thrown when converter is null. + public static void AddTypedConverter((Type, Type) key, Func converter) + { + if (converter is null) + throw new ArgumentNullException(nameof(converter)); - /// - /// Gets the typed converter function for converting between specified types. - /// - /// The source type to convert from. - /// The destination type to convert to. - /// A converter function for the specified types. - public static Func GetTypedConverter() - { - return (Func)_typedConverters[(typeof(TFrom), typeof(TTo))]; - } + _typedConverters.Add(key, converter); + _typedConverters2.Add(key, converter); + } - /// - /// Gets the typed converter function for converting between specified types. - /// - /// The source type to convert from. - /// The destination type to convert to. - /// A converter function for the specified types. - public static Func GetTypedConverter(Type from, Type to) - { - return (Func)_typedConverters[(from, to)]; - } + /// + /// Gets the typed converter function for converting between specified types. + /// + /// The source type to convert from. + /// The destination type to convert to. + /// A converter function for the specified types. + public static Func GetTypedConverter() + { + return (Func)_typedConverters[(typeof(TFrom), typeof(TTo))]; + } - /// - /// Converts the value from source type to destination type using registered type converters. - /// - /// The source type to convert from. - /// The destination type to convert to. - /// The value to convert. - /// The converted value. - public static TTo TypedTo(this TFrom from) - { - return GetTypedConverter()(from); - } + /// + /// Gets the typed converter function for converting between specified types. + /// + /// The source type to convert from. + /// The destination type to convert to. + /// A converter function for the specified types. + public static Func GetTypedConverter(Type from, Type to) + { + return (Func)_typedConverters[(from, to)]; + } - private static bool TryGetTypedConverter(Type from, Type to, out Func typedConverter) - { - return _typedConverters2.TryGetValue((from, to), out typedConverter); - } + /// + /// Converts the value from source type to destination type using registered type converters. + /// + /// The source type to convert from. + /// The destination type to convert to. + /// The value to convert. + /// The converted value. + public static TTo TypedTo(this TFrom from) + { + return GetTypedConverter()(from); + } + + private static bool TryGetTypedConverter(Type from, Type to, out Func typedConverter) + { + return _typedConverters2.TryGetValue((from, to), out typedConverter); + } + + /// + /// Convert value into a instance of . + /// + /// The value. + /// Type of the destination. + /// Converted object. + public static object To(this object value, Type destinationType) + { + if (destinationType is null) + throw new ArgumentNullException(nameof(destinationType)); - /// - /// Convert value into a instance of . - /// - /// The value. - /// Type of the destination. - /// Converted object. - public static object To(this object value, Type destinationType) + try { - if (destinationType is null) - throw new ArgumentNullException(nameof(destinationType)); + if (value is null) + { + if ((destinationType.IsValueType || destinationType.IsEnum()) && !destinationType.IsNullable()) + throw new ArgumentNullException(nameof(value)); + + return null; + } - try + Type GetValueType() { - if (value is null) - { - if ((destinationType.IsValueType || destinationType.IsEnum()) && !destinationType.IsNullable()) - throw new ArgumentNullException(nameof(value)); + if (value is Type) + return typeof(Type); + else if (value is IPAddress) + return typeof(IPAddress); + else if (value is EndPoint) + return typeof(EndPoint); + else if (value is Encoding) + return typeof(Encoding); + else if (value is CultureInfo) + return typeof(CultureInfo); + + var type = value.GetType(); + type.EnsureRunClass(); + return type; + } - return null; - } + destinationType.EnsureRunClass(); - Type GetValueType() - { - if (value is Type) - return typeof(Type); - else if (value is IPAddress) - return typeof(IPAddress); - else if (value is EndPoint) - return typeof(EndPoint); - else if (value is Encoding) - return typeof(Encoding); - else if (value is CultureInfo) - return typeof(CultureInfo); - - var type = value.GetType(); - type.EnsureRunClass(); - return type; - } + if (TryGetTypedConverter(GetValueType(), destinationType, out var typedConverter)) + return typedConverter(value); - destinationType.EnsureRunClass(); + var sourceType = value.GetType(); - if (TryGetTypedConverter(GetValueType(), destinationType, out var typedConverter)) - return typedConverter(value); + if (sourceType.Is(destinationType)) + return value; + else if ((value is string || sourceType.IsPrimitive) && destinationType.IsEnum()) + { + if (value is string s) + return Enum.Parse(destinationType, s, true); + else + return Enum.ToObject(destinationType, value); + } + else if (destinationType == typeof(Type[])) + { + if (value is not IEnumerable) + value = new[] { value }; - var sourceType = value.GetType(); + return ((IEnumerable)value).Select(arg => arg?.GetType() ?? typeof(void)).ToArray(); + } + else if (value is Stream st1 && destinationType == typeof(string)) + { + return st1.To().To(); + } + else if (value is Stream st2 && destinationType == typeof(byte[])) + { + MemoryStream output; - if (sourceType.Is(destinationType)) - return value; - else if ((value is string || sourceType.IsPrimitive) && destinationType.IsEnum()) - { - if (value is string s) - return Enum.Parse(destinationType, s, true); - else - return Enum.ToObject(destinationType, value); - } - else if (destinationType == typeof(Type[])) + if (st2 is MemoryStream memoryStream) + output = memoryStream; + else { - if (value is not IEnumerable) - value = new[] { value }; + const int capacity = FileSizes.KB * 4; - return ((IEnumerable)value).Select(arg => arg?.GetType() ?? typeof(void)).ToArray(); - } - else if (value is Stream st1 && destinationType == typeof(string)) - { - return st1.To().To(); - } - else if (value is Stream st2 && destinationType == typeof(byte[])) - { - MemoryStream output; + output = new MemoryStream(capacity); - if (st2 is MemoryStream memoryStream) - output = memoryStream; - else - { - const int capacity = FileSizes.KB * 4; + var buffer = new byte[FileSizes.KB]; - output = new MemoryStream(capacity); + //int offset = 0; - var buffer = new byte[FileSizes.KB]; + while (true) + { + int readBytes = st2.Read(buffer, 0, FileSizes.KB); + if (readBytes == 0) + break; - //int offset = 0; + output.Write(buffer, 0, readBytes); + //offset += readBytes; + } + } - while (true) - { - int readBytes = st2.Read(buffer, 0, FileSizes.KB); - if (readBytes == 0) - break; + return output.ToArray(); + } + else if (value is ArraySegment seg && (destinationType == typeof(Stream) || destinationType == typeof(MemoryStream))) + { + return new MemoryStream(seg.Array, seg.Offset, seg.Count); + } + else if (value is byte[] ba && (destinationType == typeof(Stream) || destinationType == typeof(MemoryStream))) + { + var stream = new MemoryStream(ba.Length); + stream.Write(ba, 0, stream.Capacity); + stream.Position = 0; + return stream; + } + else if (value is string && (destinationType == typeof(Stream) || destinationType == typeof(MemoryStream))) + { + return value.To().To(); + } + else if (destinationType == typeof(byte[])) + { + if (value is Enum) + value = value.To(sourceType.GetEnumBaseType()); - output.Write(buffer, 0, readBytes); - //offset += readBytes; - } - } + if (TryGetTypedConverter(GetValueType(), destinationType, out typedConverter)) + return typedConverter(value); + else if (value is Array arr && ArrayCovariance(arr, destinationType, out var dest)) + return dest; + } + else if (value is byte[] bytes) + { + Type enumType; - return output.ToArray(); - } - else if (value is ArraySegment seg && (destinationType == typeof(Stream) || destinationType == typeof(MemoryStream))) + if (destinationType.IsEnum()) { - return new MemoryStream(seg.Array, seg.Offset, seg.Count); + enumType = destinationType; + destinationType = destinationType.GetEnumBaseType(); } - else if (value is byte[] ba && (destinationType == typeof(Stream) || destinationType == typeof(MemoryStream))) + else + enumType = null; + + object retVal; + + if (TryGetTypedConverter(typeof(byte[]), destinationType, out typedConverter)) + retVal = typedConverter(value); + else if (destinationType.IsArray && ArrayCovariance(bytes, destinationType, out var dest)) + return dest; + else + throw new ArgumentException($"Can't convert byte array to '{destinationType}'.", nameof(value)); + + if (enumType != null) + retVal = Enum.ToObject(enumType, retVal); + + return retVal; + } + else if (value is DBNull) + return null; + else if (destinationType.GetUnderlyingType() is Type underlying) + { + if (value is null || (value is string s3 && s3.Length == 0)) { - var stream = new MemoryStream(ba.Length); - stream.Write(ba, 0, stream.Capacity); - stream.Position = 0; - return stream; + return destinationType.CreateInstance(); } - else if (value is string && (destinationType == typeof(Stream) || destinationType == typeof(MemoryStream))) + else { - return value.To().To(); + if (destinationType == typeof(decimal?)) + return value.To(typeof(decimal)); + else if (destinationType == typeof(int?)) + return value.To(typeof(int)); + else if (destinationType == typeof(long?)) + return value.To(typeof(long)); + else + return destinationType.CreateInstance(value.To(underlying)); } - else if (destinationType == typeof(byte[])) - { - if (value is Enum) - value = value.To(sourceType.GetEnumBaseType()); + } + else if (value is DateTime dt && destinationType == typeof(string)) + return dt.Millisecond > 0 ? dt.ToString("o") : value.ToString(); + else if (value is DateTimeOffset dto && destinationType == typeof(string)) + return dto.Millisecond > 0 ? dto.ToString("o") : value.ToString(); + else if (value is string str4 && destinationType == typeof(DateTimeOffset)) + return DateTimeOffset.Parse(str4); + else if (value is string s6 && destinationType == typeof(XDocument)) + return XDocument.Parse(s6); + else if (value is string s7 && destinationType == typeof(XElement)) + return XElement.Parse(s7); + else if (value is XNode && destinationType == typeof(string)) + return value.ToString(); + else if (value is string s8 && destinationType == typeof(XmlDocument)) + { + var doc = new XmlDocument(); + doc.LoadXml(s8); + return doc; + } + else if (value is XmlNode n1 && destinationType == typeof(string)) + return n1.OuterXml; + else + { + var attr = destinationType.GetAttribute(); - if (TryGetTypedConverter(GetValueType(), destinationType, out typedConverter)) - return typedConverter(value); - else if (value is Array arr && ArrayCovariance(arr, destinationType, out var dest)) - return dest; - } - else if (value is byte[] bytes) + if (attr != null) { - Type enumType; + var ctors = attr.ConverterTypeName.To().GetConstructors(); - if (destinationType.IsEnum()) + if (ctors.Length == 1) { - enumType = destinationType; - destinationType = destinationType.GetEnumBaseType(); + var ctor = ctors[0]; + var converter = (TypeConverter)(ctor.GetParameters().Length == 0 ? ctor.Invoke(null) : ctor.Invoke([destinationType])); + if (converter.CanConvertFrom(sourceType)) + return converter.ConvertFrom(value); } - else - enumType = null; - - object retVal; - - if (TryGetTypedConverter(typeof(byte[]), destinationType, out typedConverter)) - retVal = typedConverter(value); - else if (destinationType.IsArray && ArrayCovariance(bytes, destinationType, out var dest)) - return dest; - else - throw new ArgumentException($"Can't convert byte array to '{destinationType}'.", nameof(value)); - - if (enumType != null) - retVal = Enum.ToObject(enumType, retVal); - - return retVal; } - else if (value is DBNull) - return null; - else if (destinationType.GetUnderlyingType() is Type underlying) + + if (value is IConvertible) { - if (value is null || (value is string s3 && s3.Length == 0)) + try { - return destinationType.CreateInstance(); + return Convert.ChangeType(value, destinationType, null); } - else + catch (InvalidCastException) { - if (destinationType == typeof(decimal?)) - return value.To(typeof(decimal)); - else if (destinationType == typeof(int?)) - return value.To(typeof(int)); - else if (destinationType == typeof(long?)) - return value.To(typeof(long)); - else - return destinationType.CreateInstance(value.To(underlying)); + if (FinalTry(ref value, sourceType, destinationType)) + return value; } } - else if (value is DateTime dt && destinationType == typeof(string)) - return dt.Millisecond > 0 ? dt.ToString("o") : value.ToString(); - else if (value is DateTimeOffset dto && destinationType == typeof(string)) - return dto.Millisecond > 0 ? dto.ToString("o") : value.ToString(); - else if (value is string str4 && destinationType == typeof(DateTimeOffset)) - return DateTimeOffset.Parse(str4); - else if (value is string s6 && destinationType == typeof(XDocument)) - return XDocument.Parse(s6); - else if (value is string s7 && destinationType == typeof(XElement)) - return XElement.Parse(s7); - else if (value is XNode && destinationType == typeof(string)) - return value.ToString(); - else if (value is string s8 && destinationType == typeof(XmlDocument)) - { - var doc = new XmlDocument(); - doc.LoadXml(s8); - return doc; - } - else if (value is XmlNode n1 && destinationType == typeof(string)) - return n1.OuterXml; - else - { - var attr = destinationType.GetAttribute(); - if (attr != null) - { - var ctors = attr.ConverterTypeName.To().GetConstructors(); + if (value is Array arr && ArrayCovariance(arr, destinationType, out var dest)) + return dest; + else if (value is ICovarianceEnumerable covarEnu && destinationType.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + return covarEnu.ChangeType(destinationType.GetGenericArguments().First()); - if (ctors.Length == 1) - { - var ctor = ctors[0]; - var converter = (TypeConverter)(ctor.GetParameters().Length == 0 ? ctor.Invoke(null) : ctor.Invoke([destinationType])); - if (converter.CanConvertFrom(sourceType)) - return converter.ConvertFrom(value); - } - } + if (FinalTry(ref value, sourceType, destinationType)) + return value; + } - if (value is IConvertible) - { - try - { - return Convert.ChangeType(value, destinationType, null); - } - catch (InvalidCastException) - { - if (FinalTry(ref value, sourceType, destinationType)) - return value; - } - } + throw new ArgumentException($"Can't convert {value} of type '{value.GetType()}' to type '{destinationType}'.", nameof(value)); + } + catch (Exception ex) + { + throw new InvalidCastException($"Can't convert {value} of type '{value?.GetType()}' to type '{destinationType}'.", ex); + } + } - if (value is Array arr && ArrayCovariance(arr, destinationType, out var dest)) - return dest; - else if (value is ICovarianceEnumerable covarEnu && destinationType.GetGenericTypeDefinition() == typeof(IEnumerable<>)) - return covarEnu.ChangeType(destinationType.GetGenericArguments().First()); + private static bool ArrayCovariance(Array source, Type destinationType, out object result) + { + result = null; - if (FinalTry(ref value, sourceType, destinationType)) - return value; - } + if (destinationType.IsArray) + { + var elemType = destinationType.GetElementType(); + var destArr = Array.CreateInstance(elemType, source.Length); - throw new ArgumentException($"Can't convert {value} of type '{value.GetType()}' to type '{destinationType}'.", nameof(value)); - } - catch (Exception ex) - { - throw new InvalidCastException($"Can't convert {value} of type '{value?.GetType()}' to type '{destinationType}'.", ex); - } + for (var i = 0; i < source.Length; i++) + destArr.SetValue(source.GetValue(i).To(elemType), i); + + result = destArr; + return true; + } + else if (destinationType.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + { + var elemType = destinationType.GetGenericArguments().First(); + result = new CovarianceEnumerable(source).ChangeType(elemType); + return true; } + else + return false; + } + + private interface ICovarianceEnumerable + { + ICovarianceEnumerable ChangeType(Type newType); + } - private static bool ArrayCovariance(Array source, Type destinationType, out object result) + private class CovarianceEnumerable(Array array) : IEnumerable, ICovarianceEnumerable + { + private class CovarianceEnumerator : IEnumerator { - result = null; + private readonly Array _array; + private int _idx; - if (destinationType.IsArray) + public CovarianceEnumerator(Array array) { - var elemType = destinationType.GetElementType(); - var destArr = Array.CreateInstance(elemType, source.Length); + _array = array ?? throw new ArgumentNullException(nameof(array)); + Reset(); + } - for (var i = 0; i < source.Length; i++) - destArr.SetValue(source.GetValue(i).To(elemType), i); + public T Current => _array.GetValue(_idx).To(); - result = destArr; - return true; - } - else if (destinationType.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + object IEnumerator.Current => Current; + + void IDisposable.Dispose() { - var elemType = destinationType.GetGenericArguments().First(); - result = new CovarianceEnumerable(source).ChangeType(elemType); - return true; } - else - return false; - } - - private interface ICovarianceEnumerable - { - ICovarianceEnumerable ChangeType(Type newType); - } - private class CovarianceEnumerable(Array array) : IEnumerable, ICovarianceEnumerable - { - private class CovarianceEnumerator : IEnumerator + bool IEnumerator.MoveNext() { - private readonly Array _array; - private int _idx; + _idx++; + return _idx < _array.Length; + } - public CovarianceEnumerator(Array array) - { - _array = array ?? throw new ArgumentNullException(nameof(array)); - Reset(); - } + public void Reset() + { + _idx = -1; + } + } - public T Current => _array.GetValue(_idx).To(); + private readonly Array _array = array ?? throw new ArgumentNullException(nameof(array)); - object IEnumerator.Current => Current; + public IEnumerator GetEnumerator() + => new CovarianceEnumerator(_array); - void IDisposable.Dispose() - { - } + IEnumerator IEnumerable.GetEnumerator() + => GetEnumerator(); - bool IEnumerator.MoveNext() - { - _idx++; - return _idx < _array.Length; - } + public ICovarianceEnumerable ChangeType(Type newType) + => typeof(CovarianceEnumerable<>).Make(newType).CreateInstance(_array); + } - public void Reset() + private static bool FinalTry(ref object value, Type sourceType, Type destinationType) + { + static bool IsConversion(MethodInfo mi) + => mi.Name == "op_Implicit" || mi.Name == "op_Explicit"; + + var method = + sourceType + .GetMethods(BindingFlags.Public | BindingFlags.Static) + .FirstOrDefault(mi => IsConversion(mi) && mi.ReturnType == destinationType) + ?? + destinationType + .GetMethods(BindingFlags.Public | BindingFlags.Static) + .FirstOrDefault(mi => { - _idx = -1; - } - } + if (!IsConversion(mi)) + return false; - private readonly Array _array = array ?? throw new ArgumentNullException(nameof(array)); + var parameters = mi.GetParameters(); - public IEnumerator GetEnumerator() - => new CovarianceEnumerator(_array); + return parameters.Length == 1 && parameters[0].ParameterType == sourceType; + }); - IEnumerator IEnumerable.GetEnumerator() - => GetEnumerator(); + if (method != null) + value = method.Invoke(null, [value]); + else if (destinationType == typeof(string)) + value = value.ToString(); + else + return false; - public ICovarianceEnumerable ChangeType(Type newType) - => typeof(CovarianceEnumerable<>).Make(newType).CreateInstance(_array); - } + return true; + } - private static bool FinalTry(ref object value, Type sourceType, Type destinationType) - { - static bool IsConversion(MethodInfo mi) - => mi.Name == "op_Implicit" || mi.Name == "op_Explicit"; - - var method = - sourceType - .GetMethods(BindingFlags.Public | BindingFlags.Static) - .FirstOrDefault(mi => IsConversion(mi) && mi.ReturnType == destinationType) - ?? - destinationType - .GetMethods(BindingFlags.Public | BindingFlags.Static) - .FirstOrDefault(mi => - { - if (!IsConversion(mi)) - return false; + //private static readonly Dictionary, MethodInfo> _castOperators = new Dictionary, MethodInfo>(); - var parameters = mi.GetParameters(); + /// + /// Convert value into a instance of destinationType. + /// + /// The value. + /// Converted object. + public static T To(this object value) + { + return (T)To(value, typeof(T)); + } - return parameters.Length == 1 && parameters[0].ParameterType == sourceType; - }); + /// + /// Adds a C# language alias for a type. + /// + /// The type to add an alias for. + /// The C# alias to associate with the type. + /// Thrown when type or alias is null. + /// Thrown when alias or type already exists. + public static void AddCSharpAlias(Type type, string alias) + { + if (type is null) + throw new ArgumentNullException(nameof(type)); - if (method != null) - value = method.Invoke(null, [value]); - else if (destinationType == typeof(string)) - value = value.ToString(); - else - return false; + if (alias.IsEmpty()) + throw new ArgumentNullException(nameof(alias)); - return true; - } + if (_sharpAliases.ContainsKey(alias)) + throw new ArgumentOutOfRangeException(nameof(alias)); - //private static readonly Dictionary, MethodInfo> _castOperators = new Dictionary, MethodInfo>(); + if (_sharpAliasesByValue.ContainsKey(type)) + throw new ArgumentOutOfRangeException(nameof(type)); - /// - /// Convert value into a instance of destinationType. - /// - /// The value. - /// Converted object. - public static T To(this object value) - { - return (T)To(value, typeof(T)); - } + _sharpAliases.Add(alias, type); + _sharpAliasesByValue.Add(type, alias); + } - /// - /// Adds a C# language alias for a type. - /// - /// The type to add an alias for. - /// The C# alias to associate with the type. - /// Thrown when type or alias is null. - /// Thrown when alias or type already exists. - public static void AddCSharpAlias(Type type, string alias) - { - if (type is null) - throw new ArgumentNullException(nameof(type)); + /// + /// Attempts to get the C# language alias for a type. + /// + /// The type to get the alias for. + /// The C# alias if found; otherwise null. + public static string TryGetCSharpAlias(this Type type) + => _sharpAliasesByValue.TryGetValue(type, out var alias) ? alias : null; - if (alias.IsEmpty()) - throw new ArgumentNullException(nameof(alias)); + /// + /// Attempts to get a type by its C# language alias. + /// + /// The C# alias to look up. + /// The corresponding type if found; otherwise null. + public static Type TryGetTypeByCSharpAlias(this string alias) + => _sharpAliases.TryGetValue(alias, out var type) ? type : null; - if (_sharpAliases.ContainsKey(alias)) - throw new ArgumentOutOfRangeException(nameof(alias)); + /// + /// Executes a function using the specified culture. + /// + /// The return type of the function. + /// The culture to use during execution. + /// The function to execute. + /// The result of the function execution. + /// Thrown when cultureInfo or func is null. + public static T DoInCulture(this CultureInfo cultureInfo, Func func) + { + if (func is null) + throw new ArgumentNullException(nameof(func)); - if (_sharpAliasesByValue.ContainsKey(type)) - throw new ArgumentOutOfRangeException(nameof(type)); + var prevCi = Thread.CurrentThread.CurrentCulture; + Thread.CurrentThread.CurrentCulture = cultureInfo ?? throw new ArgumentNullException(nameof(cultureInfo)); - _sharpAliases.Add(alias, type); - _sharpAliasesByValue.Add(type, alias); + try + { + return func(); } - - /// - /// Attempts to get the C# language alias for a type. - /// - /// The type to get the alias for. - /// The C# alias if found; otherwise null. - public static string TryGetCSharpAlias(this Type type) - => _sharpAliasesByValue.TryGetValue(type, out var alias) ? alias : null; - - /// - /// Attempts to get a type by its C# language alias. - /// - /// The C# alias to look up. - /// The corresponding type if found; otherwise null. - public static Type TryGetTypeByCSharpAlias(this string alias) - => _sharpAliases.TryGetValue(alias, out var type) ? type : null; - - /// - /// Executes a function using the specified culture. - /// - /// The return type of the function. - /// The culture to use during execution. - /// The function to execute. - /// The result of the function execution. - /// Thrown when cultureInfo or func is null. - public static T DoInCulture(this CultureInfo cultureInfo, Func func) + finally { - if (func is null) - throw new ArgumentNullException(nameof(func)); - - var prevCi = Thread.CurrentThread.CurrentCulture; - Thread.CurrentThread.CurrentCulture = cultureInfo ?? throw new ArgumentNullException(nameof(cultureInfo)); - - try - { - return func(); - } - finally - { - Thread.CurrentThread.CurrentCulture = prevCi; - } + Thread.CurrentThread.CurrentCulture = prevCi; } + } - /// - /// Executes an action using the specified culture. - /// - /// The culture to use during execution. - /// The action to execute. - /// Thrown when cultureInfo or action is null. - public static void DoInCulture(this CultureInfo cultureInfo, Action action) + /// + /// Executes an action using the specified culture. + /// + /// The culture to use during execution. + /// The action to execute. + /// Thrown when cultureInfo or action is null. + public static void DoInCulture(this CultureInfo cultureInfo, Action action) + { + if (action is null) + throw new ArgumentNullException(nameof(action)); + + cultureInfo.DoInCulture(() => { - if (action is null) - throw new ArgumentNullException(nameof(action)); + action(); + return null; + }); + } - cultureInfo.DoInCulture(() => - { - action(); - return null; - }); - } + /// + /// Wraps a function to execute using the invariant culture. + /// + /// The return type of the function. + /// The function to wrap. + /// A function that executes using the invariant culture. + /// Thrown when func is null. + public static Func AsInvariant(this Func func) + { + if (func is null) + throw new ArgumentNullException(nameof(func)); - /// - /// Wraps a function to execute using the invariant culture. - /// - /// The return type of the function. - /// The function to wrap. - /// A function that executes using the invariant culture. - /// Thrown when func is null. - public static Func AsInvariant(this Func func) - { - if (func is null) - throw new ArgumentNullException(nameof(func)); + return () => Do.Invariant(func); + } - return () => Do.Invariant(func); - } + /// + /// Wraps an action to execute using the invariant culture. + /// + /// The action to wrap. + /// An action that executes using the invariant culture. + /// Thrown when action is null. + public static Action AsInvariant(this Action action) + { + if (action is null) + throw new ArgumentNullException(nameof(action)); - /// - /// Wraps an action to execute using the invariant culture. - /// - /// The action to wrap. - /// An action that executes using the invariant culture. - /// Thrown when action is null. - public static Action AsInvariant(this Action action) - { - if (action is null) - throw new ArgumentNullException(nameof(action)); + return () => Do.Invariant(action); + } - return () => Do.Invariant(action); - } + /// + /// Converts the given decimal number to the numeral system with the + /// specified radix (in the range [2, 36]). + /// + /// The number to convert. + /// The radix of the destination numeral system + /// (in the range [2, 36]). + /// + public static string ToRadix(this long decimalNumber, int radix) + { + // + // http://www.pvladov.com/2012/05/decimal-to-arbitrary-numeral-system.html + // - /// - /// Converts the given decimal number to the numeral system with the - /// specified radix (in the range [2, 36]). - /// - /// The number to convert. - /// The radix of the destination numeral system - /// (in the range [2, 36]). - /// - public static string ToRadix(this long decimalNumber, int radix) - { - // - // http://www.pvladov.com/2012/05/decimal-to-arbitrary-numeral-system.html - // + const int bitsInLong = 64; + const string digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - const int bitsInLong = 64; - const string digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + if (radix < 2 || radix > digits.Length) + throw new ArgumentOutOfRangeException(nameof(radix), radix, $"The radix must be >= 2 and <= {digits.Length}."); - if (radix < 2 || radix > digits.Length) - throw new ArgumentOutOfRangeException(nameof(radix), radix, $"The radix must be >= 2 and <= {digits.Length}."); + if (decimalNumber == 0) + return "0"; - if (decimalNumber == 0) - return "0"; + var index = bitsInLong - 1; + var currentNumber = Math.Abs(decimalNumber); + var charArray = new char[bitsInLong]; - var index = bitsInLong - 1; - var currentNumber = Math.Abs(decimalNumber); - var charArray = new char[bitsInLong]; + while (currentNumber != 0) + { + var remainder = (int)(currentNumber % radix); + charArray[index--] = digits[remainder]; + currentNumber /= radix; + } - while (currentNumber != 0) - { - var remainder = (int)(currentNumber % radix); - charArray[index--] = digits[remainder]; - currentNumber /= radix; - } + var result = new string(charArray, index + 1, bitsInLong - index - 1); - var result = new string(charArray, index + 1, bitsInLong - index - 1); + if (decimalNumber < 0) + result = "-" + result; - if (decimalNumber < 0) - result = "-" + result; + return result; + } - return result; - } + /// + /// Retrieves the host from the specified endpoint. + /// + /// The endpoint to extract the host from. + /// The host string. + /// Thrown if endPoint is null. + /// Thrown if the endpoint type is unknown. + public static string GetHost(this EndPoint endPoint) + { + if (endPoint is null) + throw new ArgumentNullException(nameof(endPoint)); - /// - /// Retrieves the host from the specified endpoint. - /// - /// The endpoint to extract the host from. - /// The host string. - /// Thrown if endPoint is null. - /// Thrown if the endpoint type is unknown. - public static string GetHost(this EndPoint endPoint) + if (endPoint is IPEndPoint ip) { - if (endPoint is null) - throw new ArgumentNullException(nameof(endPoint)); - - if (endPoint is IPEndPoint ip) - { - return ip.Address.ToString(); - } - else if (endPoint is DnsEndPoint dns) - { - return dns.Host; - } - else - throw new InvalidOperationException($"Unknown endpoint {endPoint}."); + return ip.Address.ToString(); } - - /// - /// Sets the host on the specified endpoint. - /// - /// The endpoint to modify. - /// The new host name or IP address. - /// The modified endpoint. - /// Thrown if endPoint is null. - /// Thrown if the endpoint type is unknown. - public static EndPoint SetHost(this EndPoint endPoint, string host) + else if (endPoint is DnsEndPoint dns) { - if (endPoint is null) - throw new ArgumentNullException(nameof(endPoint)); + return dns.Host; + } + else + throw new InvalidOperationException($"Unknown endpoint {endPoint}."); + } - if (endPoint is IPEndPoint ip) - { - ip.Address = host.To(); - } - else if (endPoint is DnsEndPoint dns) - { - endPoint = new DnsEndPoint(host, dns.Port, dns.AddressFamily); - } - else - throw new InvalidOperationException($"Unknown endpoint {endPoint}."); + /// + /// Sets the host on the specified endpoint. + /// + /// The endpoint to modify. + /// The new host name or IP address. + /// The modified endpoint. + /// Thrown if endPoint is null. + /// Thrown if the endpoint type is unknown. + public static EndPoint SetHost(this EndPoint endPoint, string host) + { + if (endPoint is null) + throw new ArgumentNullException(nameof(endPoint)); - return endPoint; + if (endPoint is IPEndPoint ip) + { + ip.Address = host.To(); } - - /// - /// Retrieves the port number from the specified endpoint. - /// - /// The endpoint to extract the port from. - /// The port number. - /// Thrown if endPoint is null. - /// Thrown if the endpoint type is unknown. - public static int GetPort(this EndPoint endPoint) + else if (endPoint is DnsEndPoint dns) { - if (endPoint is null) - throw new ArgumentNullException(nameof(endPoint)); - - if (endPoint is IPEndPoint ip) - { - return ip.Port; - } - else if (endPoint is DnsEndPoint dns) - { - return dns.Port; - } - else - throw new InvalidOperationException($"Unknown endpoint {endPoint}."); + endPoint = new DnsEndPoint(host, dns.Port, dns.AddressFamily); } + else + throw new InvalidOperationException($"Unknown endpoint {endPoint}."); - /// - /// Sets the port number on the specified endpoint. - /// - /// The endpoint to modify. - /// The new port number. - /// The modified endpoint. - /// Thrown if endPoint is null. - /// Thrown if the endpoint type is unknown. - public static EndPoint SetPort(this EndPoint endPoint, int port) - { - if (endPoint is null) - throw new ArgumentNullException(nameof(endPoint)); + return endPoint; + } - if (endPoint is IPEndPoint ip) - { - ip.Port = port; - } - else if (endPoint is DnsEndPoint dns) - { - endPoint = new DnsEndPoint(dns.Host, port, dns.AddressFamily); - } - else - throw new InvalidOperationException($"Unknown endpoint {endPoint}."); + /// + /// Retrieves the port number from the specified endpoint. + /// + /// The endpoint to extract the port from. + /// The port number. + /// Thrown if endPoint is null. + /// Thrown if the endpoint type is unknown. + public static int GetPort(this EndPoint endPoint) + { + if (endPoint is null) + throw new ArgumentNullException(nameof(endPoint)); - return endPoint; + if (endPoint is IPEndPoint ip) + { + return ip.Port; } - - /// - /// Reverses the byte order if the specified endianness differs from the system endianness. - /// - /// The byte array to modify. - /// The number of bytes to consider. - /// Specifies the desired endianness. - /// The starting position within the array. - /// The modified byte array. - public static byte[] ChangeOrder(this byte[] bytes, int length, bool isLittleEndian, int pos = 0) + else if (endPoint is DnsEndPoint dns) { - if (isLittleEndian == BitConverter.IsLittleEndian) - return bytes; + return dns.Port; + } + else + throw new InvalidOperationException($"Unknown endpoint {endPoint}."); + } - var end = pos + length / 2; + /// + /// Sets the port number on the specified endpoint. + /// + /// The endpoint to modify. + /// The new port number. + /// The modified endpoint. + /// Thrown if endPoint is null. + /// Thrown if the endpoint type is unknown. + public static EndPoint SetPort(this EndPoint endPoint, int port) + { + if (endPoint is null) + throw new ArgumentNullException(nameof(endPoint)); - for (var i = pos; i < end; i++) - { - var start = i; - var stop = pos + length - i - 1; + if (endPoint is IPEndPoint ip) + { + ip.Port = port; + } + else if (endPoint is DnsEndPoint dns) + { + endPoint = new DnsEndPoint(dns.Host, port, dns.AddressFamily); + } + else + throw new InvalidOperationException($"Unknown endpoint {endPoint}."); - var temp = bytes[start]; - bytes[start] = bytes[stop]; - bytes[stop] = temp; - } + return endPoint; + } + /// + /// Reverses the byte order if the specified endianness differs from the system endianness. + /// + /// The byte array to modify. + /// The number of bytes to consider. + /// Specifies the desired endianness. + /// The starting position within the array. + /// The modified byte array. + public static byte[] ChangeOrder(this byte[] bytes, int length, bool isLittleEndian, int pos = 0) + { + if (isLittleEndian == BitConverter.IsLittleEndian) return bytes; + + var end = pos + length / 2; + + for (var i = pos; i < end; i++) + { + var start = i; + var stop = pos + length - i - 1; + + var temp = bytes[start]; + bytes[start] = bytes[stop]; + bytes[stop] = temp; } + + return bytes; } } \ No newline at end of file diff --git a/Common/CountryCodes.cs b/Common/CountryCodes.cs index 60ab41f8..0c3aa712 100644 --- a/Common/CountryCodes.cs +++ b/Common/CountryCodes.cs @@ -1,1021 +1,1020 @@ -namespace Ecng.Common +namespace Ecng.Common; + +/// +/// Enumerates country codes corresponding to various countries, territories and regions. +/// +public enum CountryCodes { /// - /// Enumerates country codes corresponding to various countries, territories and regions. - /// - public enum CountryCodes - { - /// - /// Abkhazia. - /// - Abkhazia, - /// - /// Afghanistan. - /// - AF, - /// - /// Aland Islands. - /// - AX, - /// - /// Albania. - /// - AL, - /// - /// Algeria. - /// - DZ, - /// - /// American Samoa. - /// - AS, - /// - /// Andorra. - /// - AD, - /// - /// Angola. - /// - AO, - /// - /// Anguilla. - /// - AI, - /// - /// Antarctica. - /// - AQ, - /// - /// Antigua and Barbuda. - /// - AG, - /// - /// Argentina. - /// - AR, - /// - /// Armenia. - /// - AM, - /// - /// Aruba. - /// - AW, - /// - /// Australia. - /// - AU, - /// - /// Austria. - /// - AT, - /// - /// Azerbaijan. - /// - AZ, - /// - /// Bahamas. - /// - BS, - /// - /// Bahrain. - /// - BH, - /// - /// Bangladesh. - /// - BD, - /// - /// Barbados. - /// - BB, - /// - /// Basque Country. - /// - BasqueCountry, - /// - /// Belarus. - /// - BY, - /// - /// Belgium. - /// - BE, - /// - /// Belize. - /// - BZ, - /// - /// Benin. - /// - BJ, - /// - /// Bermuda. - /// - BM, - /// - /// Bhutan. - /// - BT, - /// - /// Bolivia. - /// - BO, - /// - /// Bosnia and Herzegovina. - /// - BA, - /// - /// Botswana. - /// - BW, - /// - /// Brazil. - /// - BR, - /// - /// British Indian Ocean Territory. - /// - BritishAntarcticTerritory, - /// - /// British Virgin Islands. - /// - VG, - /// - /// Brunei Darussalam. - /// - BN, - /// - /// Bulgaria. - /// - BG, - /// - /// Burkina Faso. - /// - BF, - /// - /// Burundi. - /// - BI, - /// - /// Cambodia. - /// - KH, - /// - /// Cameroon. - /// - CM, - /// - /// Canada. - /// - CA, - /// - /// Cape Verde. - /// - CV, - /// - /// Cayman Islands. - /// - KY, - /// - /// Central African Republic. - /// - CF, - /// - /// Chad. - /// - TD, - /// - /// Chile. - /// - CL, - /// - /// China. - /// - CN, - /// - /// Christmas Island. - /// - CX, - /// - /// Cocos (Keeling) Islands. - /// - CC, - /// - /// Colombia. - /// - CO, - /// - /// Commonwealth. - /// - Commonwealth, - /// - /// Comoros. - /// - KM, - /// - /// Congo (Brazzaville). - /// - CG, - /// - /// Congo, Democratic Republic of the. - /// - CD, - /// - /// Cook Islands. - /// - CK, - /// - /// Costa Rica. - /// - CR, - /// - /// Cote d'Ivoire. - /// - CI, - /// - /// Croatia. - /// - HR, - /// - /// Cuba. - /// - CU, - /// - /// Curacao. - /// - CW, - /// - /// Cyprus. - /// - CY, - /// - /// Czech Republic. - /// - CZ, - /// - /// Denmark. - /// - DK, - /// - /// Djibouti. - /// - DJ, - /// - /// Dominica. - /// - DM, - /// - /// Dominican Republic. - /// - DO, - /// - /// Ecuador. - /// - EC, - /// - /// Egypt. - /// - EG, - /// - /// El Salvador. - /// - SV, - /// - /// Equatorial Guinea. - /// - GQ, - /// - /// Eritrea. - /// - ER, - /// - /// Estonia. - /// - EE, - /// - /// Ethiopia. - /// - ET, - /// - /// European Union. - /// - EU, - /// - /// Falkland Islands (Malvinas). - /// - FK, - /// - /// Faroe Islands. - /// - FO, - /// - /// Fiji. - /// - FJ, - /// - /// Finland. - /// - FI, - /// - /// France. - /// - FR, - /// - /// French Polynesia. - /// - PF, - /// - /// French Southern Territories. - /// - TF, - /// - /// Gabon. - /// - GA, - /// - /// Gambia. - /// - GM, - /// - /// Georgia. - /// - GE, - /// - /// Germany. - /// - DE, - /// - /// Ghana. - /// - GH, - /// - /// Gibraltar. - /// - GI, - /// - /// Gosquared. - /// - Gosquared, - /// - /// Greece. - /// - GR, - /// - /// Greenland. - /// - GL, - /// - /// Grenada. - /// - GD, - /// - /// Guam. - /// - GU, - /// - /// Guatemala. - /// - GT, - /// - /// Guernsey. - /// - GG, - /// - /// Guinea. - /// - GN, - /// - /// Guinea-Bissau. - /// - GW, - /// - /// Guyana. - /// - GY, - /// - /// Haiti. - /// - HT, - /// - /// Holy See (Vatican City State). - /// - VA, - /// - /// Honduras. - /// - HN, - /// - /// Hong Kong, Special Administrative Region of China. - /// - HK, - /// - /// Hungary. - /// - HU, - /// - /// Iceland. - /// - IS, - /// - /// India. - /// - IN, - /// - /// Indonesia. - /// - ID, - /// - /// Iran, Islamic Republic of. - /// - IR, - /// - /// Iraq. - /// - IQ, - /// - /// Ireland. - /// - IE, - /// - /// Isle of Man. - /// - IM, - /// - /// Israel. - /// - IL, - /// - /// Italy. - /// - IT, - /// - /// Jamaica. - /// - JM, - /// - /// Japan. - /// - JP, - /// - /// Jersey. - /// - JE, - /// - /// Jordan. - /// - JO, - /// - /// Kazakhstan. - /// - KZ, - /// - /// Kenya. - /// - KE, - /// - /// Kiribati. - /// - KI, - /// - /// Korea, Democratic People's Republic of. - /// - KP, - /// - /// Korea, Republic of. - /// - KR, - /// - /// Kosovo. - /// - Kosovo, - /// - /// Kuwait. - /// - KW, - /// - /// Kyrgyzstan. - /// - KG, - /// - /// Lao PDR. - /// - LA, - /// - /// Latvia. - /// - LV, - /// - /// Lebanon. - /// - LB, - /// - /// Lesotho. - /// - LS, - /// - /// Liberia. - /// - LR, - /// - /// Libya. - /// - LY, - /// - /// Liechtenstein. - /// - LI, - /// - /// Lithuania. - /// - LT, - /// - /// Luxembourg. - /// - LU, - /// - /// Macao, Special Administrative Region of China. - /// - MO, - /// - /// Macedonia, Republic of. - /// - MK, - /// - /// Madagascar. - /// - MG, - /// - /// Malawi. - /// - MW, - /// - /// Malaysia. - /// - MY, - /// - /// Maldives. - /// - MV, - /// - /// Mali. - /// - ML, - /// - /// Malta. - /// - MT, - /// - /// Mars. - /// - Mars, - /// - /// Marshall Islands. - /// - MH, - /// - /// Martinique. - /// - MQ, - /// - /// Mauritania. - /// - MR, - /// - /// Mauritius. - /// - MU, - /// - /// Mayotte. - /// - YT, - /// - /// Mexico. - /// - MX, - /// - /// Micronesia, Federated States of. - /// - FM, - /// - /// Moldova. - /// - MD, - /// - /// Monaco. - /// - MC, - /// - /// Mongolia. - /// - MN, - /// - /// Montenegro. - /// - ME, - /// - /// Montserrat. - /// - MS, - /// - /// Morocco. - /// - MA, - /// - /// Mozambique. - /// - MZ, - /// - /// Myanmar. - /// - MM, - /// - /// Nagorno Karabakh. - /// - NagornoKarabakh, - /// - /// Namibia. - /// - NA, - /// - /// Nauru. - /// - NR, - /// - /// Nepal. - /// - NP, - /// - /// Netherlands. - /// - NL, - /// - /// Netherlands Antilles. - /// - AN, - /// - /// New Caledonia. - /// - NC, - /// - /// New Zealand. - /// - NZ, - /// - /// Nicaragua. - /// - NI, - /// - /// Niger. - /// - NE, - /// - /// Nigeria. - /// - NG, - /// - /// Niue. - /// - NU, - /// - /// Norfolk Island. - /// - NF, - /// - /// Northern Mariana Islands. - /// - MP, - /// - /// Norway. - /// - NO, - /// - /// Northern Cyprus. - /// - NorthernCyprus, - /// - /// Oman. - /// - OM, - /// - /// Pakistan. - /// - PK, - /// - /// Palau. - /// - PW, - /// - /// Palestinian Territory, Occupied. - /// - PS, - /// - /// Panama. - /// - PA, - /// - /// Papua New Guinea. - /// - PG, - /// - /// Paraguay. - /// - PY, - /// - /// Peru. - /// - PE, - /// - /// Philippines. - /// - PH, - /// - /// Pitcairn. - /// - PN, - /// - /// Poland. - /// - PL, - /// - /// Portugal. - /// - PT, - /// - /// Puerto Rico. - /// - PR, - /// - /// Qatar. - /// - QA, - /// - /// Romania. - /// - RO, - /// - /// Russian Federation. - /// - RU, - /// - /// Rwanda. - /// - RW, - /// - /// Saint Helena. - /// - SH, - /// - /// Saint Kitts and Nevis. - /// - KN, - /// - /// Saint Lucia. - /// - LC, - /// - /// Saint Vincent and Grenadines. - /// - VC, - /// - /// Saint-Barthelemy. - /// - BL, - /// - /// Saint-Martin (French part). - /// - MF, - /// - /// Samoa. - /// - WS, - /// - /// San Marino. - /// - SM, - /// - /// Sao Tome and Principe. - /// - ST, - /// - /// Saudi Arabia. - /// - SA, - /// - /// Scotland. - /// - Scotland, - /// - /// Senegal. - /// - SN, - /// - /// Serbia. - /// - RS, - /// - /// Seychelles. - /// - SC, - /// - /// Sierra Leone. - /// - SL, - /// - /// Singapore. - /// - SG, - /// - /// Slovakia. - /// - SK, - /// - /// Slovenia. - /// - SI, - /// - /// Solomon Islands. - /// - SB, - /// - /// Somalia. - /// - SO, - /// - /// Somaliland. - /// - Somaliland, - /// - /// South Africa. - /// - ZA, - /// - /// South Georgia and the South Sandwich Islands. - /// - GS, - /// - /// South Ossetia. - /// - SouthOssetia, - /// - /// South Sudan. - /// - SS, - /// - /// Spain. - /// - ES, - /// - /// Sri Lanka. - /// - LK, - /// - /// Sudan. - /// - SD, - /// - /// Suriname. - /// - SR, - /// - /// Swaziland. - /// - SZ, - /// - /// Sweden. - /// - SE, - /// - /// Switzerland. - /// - CH, - /// - /// Syrian Arab Republic (Syria). - /// - SY, - /// - /// Taiwan, Republic of China. - /// - TW, - /// - /// Tajikistan. - /// - TJ, - /// - /// Tanzania, United Republic of. - /// - TZ, - /// - /// Thailand. - /// - TH, - /// - /// Timor-Leste. - /// - TL, - /// - /// Togo. - /// - TG, - /// - /// Tokelau. - /// - TK, - /// - /// Tonga. - /// - TO, - /// - /// Trinidad and Tobago. - /// - TT, - /// - /// Tunisia. - /// - TN, - /// - /// Turkey. - /// - TR, - /// - /// Turkmenistan. - /// - TM, - /// - /// Turks and Caicos Islands. - /// - TC, - /// - /// Tuvalu. - /// - TV, - /// - /// Uganda. - /// - UG, - /// - /// Ukraine. - /// - UA, - /// - /// United Arab Emirates. - /// - AE, - /// - /// United Kingdom. - /// - GB, - /// - /// United States of America. - /// - US, - /// - /// Uruguay. - /// - UY, - /// - /// Uzbekistan. - /// - UZ, - /// - /// Vanuatu. - /// - VU, - /// - /// Venezuela (Bolivarian Republic of). - /// - VE, - /// - /// Viet Nam. - /// - VN, - /// - /// Virgin Islands, US. - /// - VI, - /// - /// Wales. - /// - Wales, - /// - /// Wallis and Futuna Islands. - /// - WF, - /// - /// Western Sahara. - /// - EH, - /// - /// Yemen. - /// - YE, - /// - /// Zambia. - /// - ZM, - /// - /// Zimbabwe. - /// - ZW, - } + /// Abkhazia. + /// + Abkhazia, + /// + /// Afghanistan. + /// + AF, + /// + /// Aland Islands. + /// + AX, + /// + /// Albania. + /// + AL, + /// + /// Algeria. + /// + DZ, + /// + /// American Samoa. + /// + AS, + /// + /// Andorra. + /// + AD, + /// + /// Angola. + /// + AO, + /// + /// Anguilla. + /// + AI, + /// + /// Antarctica. + /// + AQ, + /// + /// Antigua and Barbuda. + /// + AG, + /// + /// Argentina. + /// + AR, + /// + /// Armenia. + /// + AM, + /// + /// Aruba. + /// + AW, + /// + /// Australia. + /// + AU, + /// + /// Austria. + /// + AT, + /// + /// Azerbaijan. + /// + AZ, + /// + /// Bahamas. + /// + BS, + /// + /// Bahrain. + /// + BH, + /// + /// Bangladesh. + /// + BD, + /// + /// Barbados. + /// + BB, + /// + /// Basque Country. + /// + BasqueCountry, + /// + /// Belarus. + /// + BY, + /// + /// Belgium. + /// + BE, + /// + /// Belize. + /// + BZ, + /// + /// Benin. + /// + BJ, + /// + /// Bermuda. + /// + BM, + /// + /// Bhutan. + /// + BT, + /// + /// Bolivia. + /// + BO, + /// + /// Bosnia and Herzegovina. + /// + BA, + /// + /// Botswana. + /// + BW, + /// + /// Brazil. + /// + BR, + /// + /// British Indian Ocean Territory. + /// + BritishAntarcticTerritory, + /// + /// British Virgin Islands. + /// + VG, + /// + /// Brunei Darussalam. + /// + BN, + /// + /// Bulgaria. + /// + BG, + /// + /// Burkina Faso. + /// + BF, + /// + /// Burundi. + /// + BI, + /// + /// Cambodia. + /// + KH, + /// + /// Cameroon. + /// + CM, + /// + /// Canada. + /// + CA, + /// + /// Cape Verde. + /// + CV, + /// + /// Cayman Islands. + /// + KY, + /// + /// Central African Republic. + /// + CF, + /// + /// Chad. + /// + TD, + /// + /// Chile. + /// + CL, + /// + /// China. + /// + CN, + /// + /// Christmas Island. + /// + CX, + /// + /// Cocos (Keeling) Islands. + /// + CC, + /// + /// Colombia. + /// + CO, + /// + /// Commonwealth. + /// + Commonwealth, + /// + /// Comoros. + /// + KM, + /// + /// Congo (Brazzaville). + /// + CG, + /// + /// Congo, Democratic Republic of the. + /// + CD, + /// + /// Cook Islands. + /// + CK, + /// + /// Costa Rica. + /// + CR, + /// + /// Cote d'Ivoire. + /// + CI, + /// + /// Croatia. + /// + HR, + /// + /// Cuba. + /// + CU, + /// + /// Curacao. + /// + CW, + /// + /// Cyprus. + /// + CY, + /// + /// Czech Republic. + /// + CZ, + /// + /// Denmark. + /// + DK, + /// + /// Djibouti. + /// + DJ, + /// + /// Dominica. + /// + DM, + /// + /// Dominican Republic. + /// + DO, + /// + /// Ecuador. + /// + EC, + /// + /// Egypt. + /// + EG, + /// + /// El Salvador. + /// + SV, + /// + /// Equatorial Guinea. + /// + GQ, + /// + /// Eritrea. + /// + ER, + /// + /// Estonia. + /// + EE, + /// + /// Ethiopia. + /// + ET, + /// + /// European Union. + /// + EU, + /// + /// Falkland Islands (Malvinas). + /// + FK, + /// + /// Faroe Islands. + /// + FO, + /// + /// Fiji. + /// + FJ, + /// + /// Finland. + /// + FI, + /// + /// France. + /// + FR, + /// + /// French Polynesia. + /// + PF, + /// + /// French Southern Territories. + /// + TF, + /// + /// Gabon. + /// + GA, + /// + /// Gambia. + /// + GM, + /// + /// Georgia. + /// + GE, + /// + /// Germany. + /// + DE, + /// + /// Ghana. + /// + GH, + /// + /// Gibraltar. + /// + GI, + /// + /// Gosquared. + /// + Gosquared, + /// + /// Greece. + /// + GR, + /// + /// Greenland. + /// + GL, + /// + /// Grenada. + /// + GD, + /// + /// Guam. + /// + GU, + /// + /// Guatemala. + /// + GT, + /// + /// Guernsey. + /// + GG, + /// + /// Guinea. + /// + GN, + /// + /// Guinea-Bissau. + /// + GW, + /// + /// Guyana. + /// + GY, + /// + /// Haiti. + /// + HT, + /// + /// Holy See (Vatican City State). + /// + VA, + /// + /// Honduras. + /// + HN, + /// + /// Hong Kong, Special Administrative Region of China. + /// + HK, + /// + /// Hungary. + /// + HU, + /// + /// Iceland. + /// + IS, + /// + /// India. + /// + IN, + /// + /// Indonesia. + /// + ID, + /// + /// Iran, Islamic Republic of. + /// + IR, + /// + /// Iraq. + /// + IQ, + /// + /// Ireland. + /// + IE, + /// + /// Isle of Man. + /// + IM, + /// + /// Israel. + /// + IL, + /// + /// Italy. + /// + IT, + /// + /// Jamaica. + /// + JM, + /// + /// Japan. + /// + JP, + /// + /// Jersey. + /// + JE, + /// + /// Jordan. + /// + JO, + /// + /// Kazakhstan. + /// + KZ, + /// + /// Kenya. + /// + KE, + /// + /// Kiribati. + /// + KI, + /// + /// Korea, Democratic People's Republic of. + /// + KP, + /// + /// Korea, Republic of. + /// + KR, + /// + /// Kosovo. + /// + Kosovo, + /// + /// Kuwait. + /// + KW, + /// + /// Kyrgyzstan. + /// + KG, + /// + /// Lao PDR. + /// + LA, + /// + /// Latvia. + /// + LV, + /// + /// Lebanon. + /// + LB, + /// + /// Lesotho. + /// + LS, + /// + /// Liberia. + /// + LR, + /// + /// Libya. + /// + LY, + /// + /// Liechtenstein. + /// + LI, + /// + /// Lithuania. + /// + LT, + /// + /// Luxembourg. + /// + LU, + /// + /// Macao, Special Administrative Region of China. + /// + MO, + /// + /// Macedonia, Republic of. + /// + MK, + /// + /// Madagascar. + /// + MG, + /// + /// Malawi. + /// + MW, + /// + /// Malaysia. + /// + MY, + /// + /// Maldives. + /// + MV, + /// + /// Mali. + /// + ML, + /// + /// Malta. + /// + MT, + /// + /// Mars. + /// + Mars, + /// + /// Marshall Islands. + /// + MH, + /// + /// Martinique. + /// + MQ, + /// + /// Mauritania. + /// + MR, + /// + /// Mauritius. + /// + MU, + /// + /// Mayotte. + /// + YT, + /// + /// Mexico. + /// + MX, + /// + /// Micronesia, Federated States of. + /// + FM, + /// + /// Moldova. + /// + MD, + /// + /// Monaco. + /// + MC, + /// + /// Mongolia. + /// + MN, + /// + /// Montenegro. + /// + ME, + /// + /// Montserrat. + /// + MS, + /// + /// Morocco. + /// + MA, + /// + /// Mozambique. + /// + MZ, + /// + /// Myanmar. + /// + MM, + /// + /// Nagorno Karabakh. + /// + NagornoKarabakh, + /// + /// Namibia. + /// + NA, + /// + /// Nauru. + /// + NR, + /// + /// Nepal. + /// + NP, + /// + /// Netherlands. + /// + NL, + /// + /// Netherlands Antilles. + /// + AN, + /// + /// New Caledonia. + /// + NC, + /// + /// New Zealand. + /// + NZ, + /// + /// Nicaragua. + /// + NI, + /// + /// Niger. + /// + NE, + /// + /// Nigeria. + /// + NG, + /// + /// Niue. + /// + NU, + /// + /// Norfolk Island. + /// + NF, + /// + /// Northern Mariana Islands. + /// + MP, + /// + /// Norway. + /// + NO, + /// + /// Northern Cyprus. + /// + NorthernCyprus, + /// + /// Oman. + /// + OM, + /// + /// Pakistan. + /// + PK, + /// + /// Palau. + /// + PW, + /// + /// Palestinian Territory, Occupied. + /// + PS, + /// + /// Panama. + /// + PA, + /// + /// Papua New Guinea. + /// + PG, + /// + /// Paraguay. + /// + PY, + /// + /// Peru. + /// + PE, + /// + /// Philippines. + /// + PH, + /// + /// Pitcairn. + /// + PN, + /// + /// Poland. + /// + PL, + /// + /// Portugal. + /// + PT, + /// + /// Puerto Rico. + /// + PR, + /// + /// Qatar. + /// + QA, + /// + /// Romania. + /// + RO, + /// + /// Russian Federation. + /// + RU, + /// + /// Rwanda. + /// + RW, + /// + /// Saint Helena. + /// + SH, + /// + /// Saint Kitts and Nevis. + /// + KN, + /// + /// Saint Lucia. + /// + LC, + /// + /// Saint Vincent and Grenadines. + /// + VC, + /// + /// Saint-Barthelemy. + /// + BL, + /// + /// Saint-Martin (French part). + /// + MF, + /// + /// Samoa. + /// + WS, + /// + /// San Marino. + /// + SM, + /// + /// Sao Tome and Principe. + /// + ST, + /// + /// Saudi Arabia. + /// + SA, + /// + /// Scotland. + /// + Scotland, + /// + /// Senegal. + /// + SN, + /// + /// Serbia. + /// + RS, + /// + /// Seychelles. + /// + SC, + /// + /// Sierra Leone. + /// + SL, + /// + /// Singapore. + /// + SG, + /// + /// Slovakia. + /// + SK, + /// + /// Slovenia. + /// + SI, + /// + /// Solomon Islands. + /// + SB, + /// + /// Somalia. + /// + SO, + /// + /// Somaliland. + /// + Somaliland, + /// + /// South Africa. + /// + ZA, + /// + /// South Georgia and the South Sandwich Islands. + /// + GS, + /// + /// South Ossetia. + /// + SouthOssetia, + /// + /// South Sudan. + /// + SS, + /// + /// Spain. + /// + ES, + /// + /// Sri Lanka. + /// + LK, + /// + /// Sudan. + /// + SD, + /// + /// Suriname. + /// + SR, + /// + /// Swaziland. + /// + SZ, + /// + /// Sweden. + /// + SE, + /// + /// Switzerland. + /// + CH, + /// + /// Syrian Arab Republic (Syria). + /// + SY, + /// + /// Taiwan, Republic of China. + /// + TW, + /// + /// Tajikistan. + /// + TJ, + /// + /// Tanzania, United Republic of. + /// + TZ, + /// + /// Thailand. + /// + TH, + /// + /// Timor-Leste. + /// + TL, + /// + /// Togo. + /// + TG, + /// + /// Tokelau. + /// + TK, + /// + /// Tonga. + /// + TO, + /// + /// Trinidad and Tobago. + /// + TT, + /// + /// Tunisia. + /// + TN, + /// + /// Turkey. + /// + TR, + /// + /// Turkmenistan. + /// + TM, + /// + /// Turks and Caicos Islands. + /// + TC, + /// + /// Tuvalu. + /// + TV, + /// + /// Uganda. + /// + UG, + /// + /// Ukraine. + /// + UA, + /// + /// United Arab Emirates. + /// + AE, + /// + /// United Kingdom. + /// + GB, + /// + /// United States of America. + /// + US, + /// + /// Uruguay. + /// + UY, + /// + /// Uzbekistan. + /// + UZ, + /// + /// Vanuatu. + /// + VU, + /// + /// Venezuela (Bolivarian Republic of). + /// + VE, + /// + /// Viet Nam. + /// + VN, + /// + /// Virgin Islands, US. + /// + VI, + /// + /// Wales. + /// + Wales, + /// + /// Wallis and Futuna Islands. + /// + WF, + /// + /// Western Sahara. + /// + EH, + /// + /// Yemen. + /// + YE, + /// + /// Zambia. + /// + ZM, + /// + /// Zimbabwe. + /// + ZW, } diff --git a/Common/CryptoAttribute.cs b/Common/CryptoAttribute.cs index 6b7b0a24..68837879 100644 --- a/Common/CryptoAttribute.cs +++ b/Common/CryptoAttribute.cs @@ -1,12 +1,11 @@ -namespace Ecng.Common -{ - using System; +namespace Ecng.Common; + +using System; - /// - /// The member marked as associated with crypto currency. - /// - [AttributeUsage(AttributeTargets.All)] - public class CryptoAttribute : Attribute - { - } +/// +/// The member marked as associated with crypto currency. +/// +[AttributeUsage(AttributeTargets.All)] +public class CryptoAttribute : Attribute +{ } \ No newline at end of file diff --git a/Common/CsvFile.cs b/Common/CsvFile.cs index 34d226b0..23d55cc7 100644 --- a/Common/CsvFile.cs +++ b/Common/CsvFile.cs @@ -14,370 +14,369 @@ using System.IO; using System.Text; -namespace Ecng.Common +namespace Ecng.Common; + +/// +/// Determines how empty lines are interpreted when reading CSV files. +/// These values do not affect empty lines that occur within quoted fields +/// or empty lines that appear at the end of the input file. +/// +public enum EmptyLineBehavior { /// - /// Determines how empty lines are interpreted when reading CSV files. - /// These values do not affect empty lines that occur within quoted fields - /// or empty lines that appear at the end of the input file. + /// Empty lines are interpreted as a line with zero columns. /// - public enum EmptyLineBehavior + NoColumns, + /// + /// Empty lines are interpreted as a line with a single empty column. + /// + EmptyColumn, + /// + /// Empty lines are skipped over as though they did not exist. + /// + Ignore, + /// + /// An empty line is interpreted as the end of the input file. + /// + EndOfFile, +} + +/// +/// Common base class for CSV reader and writer classes. +/// +public abstract class CsvFileCommon : Disposable +{ + /// + /// These are special characters in CSV files. If a column contains any + /// of these characters, the entire column is wrapped in double quotes. + /// + protected char[] SpecialChars = [';', '"', '\r', '\n']; + + // Indexes into SpecialChars for characters with specific meaning + private const int _delimiterIndex = 0; + private const int _quoteIndex = 1; + + /// + /// Gets/sets the character used for column delimiters. + /// + public char Delimiter { - /// - /// Empty lines are interpreted as a line with zero columns. - /// - NoColumns, - /// - /// Empty lines are interpreted as a line with a single empty column. - /// - EmptyColumn, - /// - /// Empty lines are skipped over as though they did not exist. - /// - Ignore, - /// - /// An empty line is interpreted as the end of the input file. - /// - EndOfFile, + get => SpecialChars[_delimiterIndex]; + set => SpecialChars[_delimiterIndex] = value; } /// - /// Common base class for CSV reader and writer classes. + /// Gets/sets the character used for column quotes. /// - public abstract class CsvFileCommon : Disposable + public char Quote { - /// - /// These are special characters in CSV files. If a column contains any - /// of these characters, the entire column is wrapped in double quotes. - /// - protected char[] SpecialChars = [';', '"', '\r', '\n']; - - // Indexes into SpecialChars for characters with specific meaning - private const int _delimiterIndex = 0; - private const int _quoteIndex = 1; - - /// - /// Gets/sets the character used for column delimiters. - /// - public char Delimiter - { - get => SpecialChars[_delimiterIndex]; - set => SpecialChars[_delimiterIndex] = value; - } - - /// - /// Gets/sets the character used for column quotes. - /// - public char Quote - { - get => SpecialChars[_quoteIndex]; - set => SpecialChars[_quoteIndex] = value; - } + get => SpecialChars[_quoteIndex]; + set => SpecialChars[_quoteIndex] = value; } +} + +/// +/// Class for reading from comma-separated-value (CSV) files +/// +public class CsvFileReader : CsvFileCommon, IDisposable +{ + // Private members + private readonly TextReader _reader; + private int _currPos; + private readonly EmptyLineBehavior _emptyLineBehavior; + private readonly string _lineSeparator; + + /// + /// The current line being read. + /// + public string CurrLine { get; private set; } /// - /// Class for reading from comma-separated-value (CSV) files + /// Initializes a new instance of the CsvFileReader class for the + /// specified stream. /// - public class CsvFileReader : CsvFileCommon, IDisposable + /// The stream to read from + /// The line separator to use. + /// Determines how empty lines are handled + public CsvFileReader(Stream stream, + string lineSeparator, + EmptyLineBehavior emptyLineBehavior = EmptyLineBehavior.NoColumns) + : this(new StreamReader(stream), lineSeparator, emptyLineBehavior) { - // Private members - private readonly TextReader _reader; - private int _currPos; - private readonly EmptyLineBehavior _emptyLineBehavior; - private readonly string _lineSeparator; - - /// - /// The current line being read. - /// - public string CurrLine { get; private set; } - - /// - /// Initializes a new instance of the CsvFileReader class for the - /// specified stream. - /// - /// The stream to read from - /// The line separator to use. - /// Determines how empty lines are handled - public CsvFileReader(Stream stream, - string lineSeparator, - EmptyLineBehavior emptyLineBehavior = EmptyLineBehavior.NoColumns) - : this(new StreamReader(stream), lineSeparator, emptyLineBehavior) - { - } + } - /// - /// Initializes a new instance of the CsvFileReader class for the - /// specified file path. - /// - /// The name of the CSV file to read from - /// The line separator to use. - /// Determines how empty lines are handled - public CsvFileReader(string path, - string lineSeparator, - EmptyLineBehavior emptyLineBehavior = EmptyLineBehavior.NoColumns) - : this(new StreamReader(path), lineSeparator, emptyLineBehavior) - { - } + /// + /// Initializes a new instance of the CsvFileReader class for the + /// specified file path. + /// + /// The name of the CSV file to read from + /// The line separator to use. + /// Determines how empty lines are handled + public CsvFileReader(string path, + string lineSeparator, + EmptyLineBehavior emptyLineBehavior = EmptyLineBehavior.NoColumns) + : this(new StreamReader(path), lineSeparator, emptyLineBehavior) + { + } - /// - /// Initializes a new instance of the CsvFileReader class for the - /// specified file path. - /// - /// The file reader. - /// The line separator to use. - /// Determines how empty lines are handled - public CsvFileReader(TextReader reader, - string lineSeparator, - EmptyLineBehavior emptyLineBehavior = EmptyLineBehavior.NoColumns) - { - if (lineSeparator.IsEmpty()) - throw new ArgumentNullException(nameof(lineSeparator)); + /// + /// Initializes a new instance of the CsvFileReader class for the + /// specified file path. + /// + /// The file reader. + /// The line separator to use. + /// Determines how empty lines are handled + public CsvFileReader(TextReader reader, + string lineSeparator, + EmptyLineBehavior emptyLineBehavior = EmptyLineBehavior.NoColumns) + { + if (lineSeparator.IsEmpty()) + throw new ArgumentNullException(nameof(lineSeparator)); - _reader = reader; - _lineSeparator = lineSeparator; - _emptyLineBehavior = emptyLineBehavior; - } + _reader = reader; + _lineSeparator = lineSeparator; + _emptyLineBehavior = emptyLineBehavior; + } - /// - /// Reads a row of columns from the current CSV file. Returns false if no - /// more data could be read because the end of the file was reached. - /// - /// Collection to hold the columns read - public bool ReadRow(List columns) + /// + /// Reads a row of columns from the current CSV file. Returns false if no + /// more data could be read because the end of the file was reached. + /// + /// Collection to hold the columns read + public bool ReadRow(List columns) + { + // Verify required argument + if (columns is null) + throw new ArgumentNullException(nameof(columns)); + + ReadNextLine: + // Read next line from the file + CurrLine = _reader.ReadLine(); + _currPos = 0; + // Test for end of file + if (CurrLine is null) + return false; + // Test for empty line + if (CurrLine.Length == 0) { - // Verify required argument - if (columns is null) - throw new ArgumentNullException(nameof(columns)); - - ReadNextLine: - // Read next line from the file - CurrLine = _reader.ReadLine(); - _currPos = 0; - // Test for end of file - if (CurrLine is null) - return false; - // Test for empty line - if (CurrLine.Length == 0) + switch (_emptyLineBehavior) { - switch (_emptyLineBehavior) - { - case EmptyLineBehavior.NoColumns: - columns.Clear(); - return true; - case EmptyLineBehavior.Ignore: - goto ReadNextLine; - case EmptyLineBehavior.EndOfFile: - return false; - } + case EmptyLineBehavior.NoColumns: + columns.Clear(); + return true; + case EmptyLineBehavior.Ignore: + goto ReadNextLine; + case EmptyLineBehavior.EndOfFile: + return false; } - - // Parse line - string column; - int numColumns = 0; - while (true) - { - // Read next column - if (_currPos < CurrLine.Length && CurrLine[_currPos] == Quote) - column = ReadQuotedColumn(); - else - column = ReadUnquotedColumn(); - // Add column to list - if (numColumns < columns.Count) - columns[numColumns] = column; - else - columns.Add(column); - numColumns++; - // Break if we reached the end of the line - if (CurrLine is null || _currPos == CurrLine.Length) - break; - // Otherwise skip delimiter - Debug.Assert(CurrLine[_currPos] == Delimiter); - _currPos++; - } - // Remove any unused columns from collection - if (numColumns < columns.Count) - columns.RemoveRange(numColumns, columns.Count - numColumns); - // Indicate success - return true; } - /// - /// Reads a quoted column by reading from the current line until a - /// closing quote is found or the end of the file is reached. On return, - /// the current position points to the delimiter or the end of the last - /// line in the file. Note: CurrLine may be set to null on return. - /// - private string ReadQuotedColumn() + // Parse line + string column; + int numColumns = 0; + while (true) { - // Skip opening quote character - Debug.Assert(_currPos < CurrLine.Length && CurrLine[_currPos] == Quote); + // Read next column + if (_currPos < CurrLine.Length && CurrLine[_currPos] == Quote) + column = ReadQuotedColumn(); + else + column = ReadUnquotedColumn(); + // Add column to list + if (numColumns < columns.Count) + columns[numColumns] = column; + else + columns.Add(column); + numColumns++; + // Break if we reached the end of the line + if (CurrLine is null || _currPos == CurrLine.Length) + break; + // Otherwise skip delimiter + Debug.Assert(CurrLine[_currPos] == Delimiter); _currPos++; + } + // Remove any unused columns from collection + if (numColumns < columns.Count) + columns.RemoveRange(numColumns, columns.Count - numColumns); + // Indicate success + return true; + } - // Parse column - StringBuilder builder = new(); - while (true) + /// + /// Reads a quoted column by reading from the current line until a + /// closing quote is found or the end of the file is reached. On return, + /// the current position points to the delimiter or the end of the last + /// line in the file. Note: CurrLine may be set to null on return. + /// + private string ReadQuotedColumn() + { + // Skip opening quote character + Debug.Assert(_currPos < CurrLine.Length && CurrLine[_currPos] == Quote); + _currPos++; + + // Parse column + StringBuilder builder = new(); + while (true) + { + while (_currPos == CurrLine.Length) { - while (_currPos == CurrLine.Length) - { - // End of line so attempt to read the next line - CurrLine = _reader.ReadLine(); - _currPos = 0; - // Done if we reached the end of the file - if (CurrLine is null) - return builder.ToString(); - // Otherwise, treat as a multi-line field - builder.Append(_lineSeparator); - } - - // Test for quote character - if (CurrLine[_currPos] == Quote) - { - // If two quotes, skip first and treat second as literal - int nextPos = (_currPos + 1); - if (nextPos < CurrLine.Length && CurrLine[nextPos] == Quote) - _currPos++; - else - break; // Single quote ends quoted sequence - } - // Add current character to the column - builder.Append(CurrLine[_currPos++]); + // End of line so attempt to read the next line + CurrLine = _reader.ReadLine(); + _currPos = 0; + // Done if we reached the end of the file + if (CurrLine is null) + return builder.ToString(); + // Otherwise, treat as a multi-line field + builder.Append(_lineSeparator); } - if (_currPos < CurrLine.Length) + // Test for quote character + if (CurrLine[_currPos] == Quote) { - // Consume closing quote - Debug.Assert(CurrLine[_currPos] == Quote); - _currPos++; - // Append any additional characters appearing before next delimiter - builder.Append(ReadUnquotedColumn()); + // If two quotes, skip first and treat second as literal + int nextPos = (_currPos + 1); + if (nextPos < CurrLine.Length && CurrLine[nextPos] == Quote) + _currPos++; + else + break; // Single quote ends quoted sequence } - // Return column value - return builder.ToString(); + // Add current character to the column + builder.Append(CurrLine[_currPos++]); } - /// - /// Reads an unquoted column by reading from the current line until a - /// delimiter is found or the end of the line is reached. On return, the - /// current position points to the delimiter or the end of the current - /// line. - /// - private string ReadUnquotedColumn() + if (_currPos < CurrLine.Length) { - int startPos = _currPos; - _currPos = CurrLine.IndexOf(Delimiter, _currPos); - if (_currPos == -1) - _currPos = CurrLine.Length; - if (_currPos > startPos) - return CurrLine.Substring(startPos, _currPos - startPos); - return string.Empty; - } - - /// - protected override void DisposeManaged() - { - _reader.Dispose(); - base.DisposeManaged(); + // Consume closing quote + Debug.Assert(CurrLine[_currPos] == Quote); + _currPos++; + // Append any additional characters appearing before next delimiter + builder.Append(ReadUnquotedColumn()); } + // Return column value + return builder.ToString(); } /// - /// Class for writing to comma-separated-value (CSV) files. + /// Reads an unquoted column by reading from the current line until a + /// delimiter is found or the end of the line is reached. On return, the + /// current position points to the delimiter or the end of the current + /// line. /// - public class CsvFileWriter : CsvFileCommon, IDisposable + private string ReadUnquotedColumn() { - private readonly StreamWriter _writer; - - // Private members - private string _oneQuote; - private string _twoQuotes; - private string _quotedFormat; - - /// - /// Initializes a new instance of the CsvFileWriter class for the - /// specified stream. - /// - /// The stream to write to - /// The text encoding. - public CsvFileWriter(Stream stream, Encoding encoding = null) { - _writer = encoding != null ? - new StreamWriter(stream, encoding) : - new StreamWriter(stream); - } + int startPos = _currPos; + _currPos = CurrLine.IndexOf(Delimiter, _currPos); + if (_currPos == -1) + _currPos = CurrLine.Length; + if (_currPos > startPos) + return CurrLine.Substring(startPos, _currPos - startPos); + return string.Empty; + } - /// - /// Initializes a new instance of the CsvFileWriter class for the - /// specified file path. - /// - /// The name of the CSV file to write to - public CsvFileWriter(string path) - { - _writer = new StreamWriter(path); - } + /// + protected override void DisposeManaged() + { + _reader.Dispose(); + base.DisposeManaged(); + } +} - /// - /// Writes a row of columns to the current CSV file. - /// - /// The list of columns to write - public void WriteRow(IEnumerable columns) - { - // Verify required argument - if (columns is null) - throw new ArgumentNullException(nameof(columns)); +/// +/// Class for writing to comma-separated-value (CSV) files. +/// +public class CsvFileWriter : CsvFileCommon, IDisposable +{ + private readonly StreamWriter _writer; - var i = 0; + // Private members + private string _oneQuote; + private string _twoQuotes; + private string _quotedFormat; - // Write each column - foreach (var c in columns) - { - // Add delimiter if this isn't the first column - if (i > 0) - _writer.Write(Delimiter); + /// + /// Initializes a new instance of the CsvFileWriter class for the + /// specified stream. + /// + /// The stream to write to + /// The text encoding. + public CsvFileWriter(Stream stream, Encoding encoding = null) { + _writer = encoding != null ? + new StreamWriter(stream, encoding) : + new StreamWriter(stream); + } - // Write this column - _writer.Write(Encode(c ?? string.Empty)); - i++; - } + /// + /// Initializes a new instance of the CsvFileWriter class for the + /// specified file path. + /// + /// The name of the CSV file to write to + public CsvFileWriter(string path) + { + _writer = new StreamWriter(path); + } - _writer.WriteLine(); - } + /// + /// Writes a row of columns to the current CSV file. + /// + /// The list of columns to write + public void WriteRow(IEnumerable columns) + { + // Verify required argument + if (columns is null) + throw new ArgumentNullException(nameof(columns)); + + var i = 0; - /// - /// Encodes a column's value for output. - /// - /// - /// - public string Encode(string column) + // Write each column + foreach (var c in columns) { - // Ensure we're using current quote character - if (_oneQuote is null || _oneQuote[0] != Quote) - { - _oneQuote = $"{Quote}"; - _twoQuotes = string.Format("{0}{0}", Quote); - _quotedFormat = string.Format("{0}{{0}}{0}", Quote); - } + // Add delimiter if this isn't the first column + if (i > 0) + _writer.Write(Delimiter); // Write this column - if (column.IndexOfAny(SpecialChars) != -1) - column = _quotedFormat.Put(column.Replace(_oneQuote, _twoQuotes)); - - return column; + _writer.Write(Encode(c ?? string.Empty)); + i++; } - /// - /// Clears all buffers for the current writer and causes any buffered data to be written to the underlying stream. - /// - public void Flush() => _writer.Flush(); - - /// - /// Truncates the underlying stream used by the by clearing its content. - /// - public void Truncate() => _writer.Truncate(); + _writer.WriteLine(); + } - /// - protected override void DisposeManaged() + /// + /// Encodes a column's value for output. + /// + /// + /// + public string Encode(string column) + { + // Ensure we're using current quote character + if (_oneQuote is null || _oneQuote[0] != Quote) { - _writer.Dispose(); - base.DisposeManaged(); + _oneQuote = $"{Quote}"; + _twoQuotes = string.Format("{0}{0}", Quote); + _quotedFormat = string.Format("{0}{{0}}{0}", Quote); } + + // Write this column + if (column.IndexOfAny(SpecialChars) != -1) + column = _quotedFormat.Put(column.Replace(_oneQuote, _twoQuotes)); + + return column; + } + + /// + /// Clears all buffers for the current writer and causes any buffered data to be written to the underlying stream. + /// + public void Flush() => _writer.Flush(); + + /// + /// Truncates the underlying stream used by the by clearing its content. + /// + public void Truncate() => _writer.Truncate(); + + /// + protected override void DisposeManaged() + { + _writer.Dispose(); + base.DisposeManaged(); } } diff --git a/Common/Currency.cs b/Common/Currency.cs index fc0b5254..4e357fb4 100644 --- a/Common/Currency.cs +++ b/Common/Currency.cs @@ -1,137 +1,136 @@ -namespace Ecng.Common +namespace Ecng.Common; + +using System; +using System.Runtime.Serialization; + +/// +/// Currency. +/// +[DataContract] +[Serializable] +public class Currency : Equatable { - using System; - using System.Runtime.Serialization; + /// + /// Currency type. The default is . + /// + [DataMember] + public CurrencyTypes Type { get; set; } = CurrencyTypes.USD; + + /// + /// Absolute value in . + /// + [DataMember] + public decimal Value { get; set; } + + /// + /// Create a copy of . + /// + /// Copy. + public override Currency Clone() => new() { Type = Type, Value = Value }; + + /// + /// Compare on the equivalence. + /// + /// Another value with which to compare. + /// , if the specified object is equal to the current object, otherwise, . + protected override bool OnEquals(Currency other) + => Type == other.Type && Value == other.Value; + + /// + /// Get the hash code of the object . + /// + /// A hash code. + public override int GetHashCode() => Type.GetHashCode() ^ Value.GetHashCode(); + + /// + public override string ToString() => $"{Value} {Type}"; + + /// + /// Cast object to the type . + /// + /// value. + /// Object . + [Obsolete] + public static implicit operator Currency(decimal value) => new() { Value = value }; + + /// + /// Cast object from to . + /// + /// Object . + /// value. + [Obsolete] + public static explicit operator decimal(Currency value) + { + if (value is null) + throw new ArgumentNullException(nameof(value)); + + return value.Value; + } + + private static void CheckArgs(Currency c1, Currency c2) + { + if (c1 is null) + throw new ArgumentNullException(nameof(c1)); + + if (c2 is null) + throw new ArgumentNullException(nameof(c2)); + + if (c1.Type != c2.Type) + throw new InvalidOperationException($"c1.{c1.Type} != c2.{c2.Type}"); + } /// - /// Currency. + /// Add the two objects . /// - [DataContract] - [Serializable] - public class Currency : Equatable + /// First object . + /// Second object . + /// The result of addition. + /// + /// The values must be the same . + /// + public static Currency operator +(Currency c1, Currency c2) { - /// - /// Currency type. The default is . - /// - [DataMember] - public CurrencyTypes Type { get; set; } = CurrencyTypes.USD; - - /// - /// Absolute value in . - /// - [DataMember] - public decimal Value { get; set; } - - /// - /// Create a copy of . - /// - /// Copy. - public override Currency Clone() => new() { Type = Type, Value = Value }; - - /// - /// Compare on the equivalence. - /// - /// Another value with which to compare. - /// , if the specified object is equal to the current object, otherwise, . - protected override bool OnEquals(Currency other) - => Type == other.Type && Value == other.Value; - - /// - /// Get the hash code of the object . - /// - /// A hash code. - public override int GetHashCode() => Type.GetHashCode() ^ Value.GetHashCode(); - - /// - public override string ToString() => $"{Value} {Type}"; - - /// - /// Cast object to the type . - /// - /// value. - /// Object . - [Obsolete] - public static implicit operator Currency(decimal value) => new() { Value = value }; - - /// - /// Cast object from to . - /// - /// Object . - /// value. - [Obsolete] - public static explicit operator decimal(Currency value) - { - if (value is null) - throw new ArgumentNullException(nameof(value)); - - return value.Value; - } - - private static void CheckArgs(Currency c1, Currency c2) - { - if (c1 is null) - throw new ArgumentNullException(nameof(c1)); - - if (c2 is null) - throw new ArgumentNullException(nameof(c2)); - - if (c1.Type != c2.Type) - throw new InvalidOperationException($"c1.{c1.Type} != c2.{c2.Type}"); - } - - /// - /// Add the two objects . - /// - /// First object . - /// Second object . - /// The result of addition. - /// - /// The values must be the same . - /// - public static Currency operator +(Currency c1, Currency c2) - { - CheckArgs(c1, c2); - - return (c1.Value + c2.Value).ToCurrency(c1.Type); - } - - /// - /// Subtract one value from another value. - /// - /// First object . - /// Second object . - /// The result of the subtraction. - public static Currency operator -(Currency c1, Currency c2) - { - CheckArgs(c1, c2); - - return (c1.Value - c2.Value).ToCurrency(c1.Type); - } - - /// - /// Multiply one value to another. - /// - /// First object . - /// Second object . - /// The result of the multiplication. - public static Currency operator *(Currency c1, Currency c2) - { - CheckArgs(c1, c2); - - return (c1.Value * c2.Value).ToCurrency(c1.Type); - } - - /// - /// Divide one value to another. - /// - /// First object . - /// Second object . - /// The result of the division. - public static Currency operator /(Currency c1, Currency c2) - { - CheckArgs(c1, c2); - - return (c1.Value / c2.Value).ToCurrency(c1.Type); - } + CheckArgs(c1, c2); + + return (c1.Value + c2.Value).ToCurrency(c1.Type); + } + + /// + /// Subtract one value from another value. + /// + /// First object . + /// Second object . + /// The result of the subtraction. + public static Currency operator -(Currency c1, Currency c2) + { + CheckArgs(c1, c2); + + return (c1.Value - c2.Value).ToCurrency(c1.Type); + } + + /// + /// Multiply one value to another. + /// + /// First object . + /// Second object . + /// The result of the multiplication. + public static Currency operator *(Currency c1, Currency c2) + { + CheckArgs(c1, c2); + + return (c1.Value * c2.Value).ToCurrency(c1.Type); + } + + /// + /// Divide one value to another. + /// + /// First object . + /// Second object . + /// The result of the division. + public static Currency operator /(Currency c1, Currency c2) + { + CheckArgs(c1, c2); + + return (c1.Value / c2.Value).ToCurrency(c1.Type); } } \ No newline at end of file diff --git a/Common/CurrencyHelper.cs b/Common/CurrencyHelper.cs index 51175209..f37f7b92 100644 --- a/Common/CurrencyHelper.cs +++ b/Common/CurrencyHelper.cs @@ -1,47 +1,46 @@ -namespace Ecng.Common +namespace Ecng.Common; + +/// +/// Extension class for . +/// +public static class CurrencyHelper { /// - /// Extension class for . + /// Determines the specified type is crypto currency. /// - public static class CurrencyHelper - { - /// - /// Determines the specified type is crypto currency. - /// - /// Currency type. - /// Check result. - public static bool IsCrypto(this CurrencyTypes type) - => type.GetAttributeOfType() != null; + /// Currency type. + /// Check result. + public static bool IsCrypto(this CurrencyTypes type) + => type.GetAttributeOfType() != null; - /// - /// Cast to . - /// - /// Currency value. - /// Currency type. - /// Currency. - public static Currency ToCurrency(this decimal value, CurrencyTypes type) - => new() { Type = type, Value = value }; + /// + /// Cast to . + /// + /// Currency value. + /// Currency type. + /// Currency. + public static Currency ToCurrency(this decimal value, CurrencyTypes type) + => new() { Type = type, Value = value }; - /// - /// Get the currency symbol. - /// - /// - /// The currency symbol. - public static string GetPrefix(this CurrencyTypes currency) - => currency switch - { - CurrencyTypes.USD => "$", - CurrencyTypes.EUR => "€", - CurrencyTypes.RUB => "₽", - CurrencyTypes.GBP => "£", - CurrencyTypes.JPY or CurrencyTypes.CNY => "¥", - CurrencyTypes.THB => "฿", - CurrencyTypes.BTC => "₿", - CurrencyTypes.CHF => "₣", - CurrencyTypes.INR => "₹", - CurrencyTypes.AED => "د.إ", - CurrencyTypes.KRW => "₩", - _ => currency.ToString(), - }; - } + /// + /// Get the currency symbol. + /// + /// + /// The currency symbol. + public static string GetPrefix(this CurrencyTypes currency) + => currency switch + { + CurrencyTypes.USD => "$", + CurrencyTypes.EUR => "€", + CurrencyTypes.RUB => "₽", + CurrencyTypes.GBP => "£", + CurrencyTypes.JPY or CurrencyTypes.CNY => "¥", + CurrencyTypes.THB => "฿", + CurrencyTypes.BTC => "₿", + CurrencyTypes.CHF => "₣", + CurrencyTypes.INR => "₹", + CurrencyTypes.AED => "د.إ", + CurrencyTypes.KRW => "₩", + _ => currency.ToString(), + }; } \ No newline at end of file diff --git a/Common/CurrencyTypes.cs b/Common/CurrencyTypes.cs index b4396568..5ce41c20 100644 --- a/Common/CurrencyTypes.cs +++ b/Common/CurrencyTypes.cs @@ -1,1540 +1,1539 @@ -namespace Ecng.Common +namespace Ecng.Common; + +using System; +using System.Runtime.Serialization; +using System.Xml.Serialization; + +/// +/// Currency type. +/// +/// +/// The codes are set in accordance with the ISO 4217 Currency Codes. +/// +[Serializable] +[XmlType(Namespace = "http://www.webserviceX.NET/")] +[DataContract] +public enum CurrencyTypes { - using System; - using System.Runtime.Serialization; - using System.Xml.Serialization; +// ReSharper disable InconsistentNaming + /// + /// Afghanistan, Afghanis. + /// + [EnumMember] + AFA, + + /// + /// Turkmenistan, Turkmenistani manat. + /// + [EnumMember] + TMT, + + /// + /// Uzbekistan, Uzbekistan som. + /// + [EnumMember] + UZS, + + /// + /// Tajikistan, Somoni. + /// + [EnumMember] + TJS, + + /// + /// Armenia, Armenian dram. + /// + [EnumMember] + AMD, + + /// + /// International Monetary Fund, Special Drawing Rights. + /// + [EnumMember] + XDR, + + /// + /// Azerbaijan, Azerbaijani manat. + /// + [EnumMember] + AZN, + + /// + /// Belarus, Belarusian ruble. + /// + [EnumMember] + BYR, + + /// + /// Belarus, Belarusian ruble. + /// + [EnumMember] + BYN, + + /// + /// Romania, Romanian new leu. + /// + [EnumMember] + RON, + + /// + /// Bulgaria, Bulgarian lev. + /// + [EnumMember] + BGN, + + /// + /// Kyrgyzstan, Kyrgyzstani som. + /// + [EnumMember] + KGS, + + /// + /// Albania, Leke. + /// + [EnumMember] + ALL, + + /// + /// Algeria, Algeria Dinars. + /// + [EnumMember] + DZD, + + /// + /// Argentina, Pesos. + /// + [EnumMember] + ARS, + + /// + /// Aruba, Guilders (also called Florins). + /// + [EnumMember] + AWG, + + /// + /// Australia, Dollars. + /// + [EnumMember] + AUD, + + /// + /// Bahamas, Dollars. + /// + [EnumMember] + BSD, + + /// + /// Bahrain, Dinars. + /// + [EnumMember] + BHD, + + /// + /// Bangladesh, Taka. + /// + [EnumMember] + BDT, + + /// + /// Barbados, Dollars. + /// + [EnumMember] + BBD, + + /// + /// Belize, Dollars. + /// + [EnumMember] + BZD, + + /// + /// Bermuda, Dollars. + /// + [EnumMember] + BMD, + + /// + /// Bhutan, Ngultrum. + /// + [EnumMember] + BTN, + + /// + /// Bolivia, Bolivianos. + /// + [EnumMember] + BOB, + + /// + /// Botswana, Pulas. + /// + [EnumMember] + BWP, + + /// + /// Brazil, Brazil Real. + /// + [EnumMember] + BRL, + + /// + /// United Kingdom, Pounds sterling. + /// + [EnumMember] + GBP, + + /// + /// Brunei Darussalam, Dollars. + /// + [EnumMember] + BND, + + /// + /// Burundi, Francs. + /// + [EnumMember] + BIF, + + /// + /// Communaute Financiere Africaine BCEAO, Francs. + /// + [EnumMember] + XOF, + + /// + /// Communaute Financiere Africaine BEAC, Francs. + /// + [EnumMember] + XAF, + + /// + /// Cambodia, Riels. + /// + [EnumMember] + KHR, + + /// + /// Canada, Dollars. + /// + [EnumMember] + CAD, + + /// + /// Cape Verde, Escudos. + /// + [EnumMember] + CVE, + + /// + /// Cayman Islands, Dollars. + /// + [EnumMember] + KYD, + + /// + /// Chile, Pesos. + /// + [EnumMember] + CLP, + + /// + /// China, Yuan Renminbi. + /// + [EnumMember] + CNY, + + /// + /// China, offshore RMB. + /// + [EnumMember] + CNH, + + /// + /// Colombia, Pesos. + /// + [EnumMember] + COP, + + /// + /// Comoros, Francs. + /// + [EnumMember] + KMF, + + /// + /// Costa Rica, Colones. + /// + [EnumMember] + CRC, + + /// + /// Croatia, Kuna. + /// + [EnumMember] + HRK, + + /// + /// Cuba, Pesos. + /// + [EnumMember] + CUP, + + /// + /// Cyprus, Pounds. + /// + [EnumMember] + CYP, + + /// + /// Czech Republic, Koruny. + /// + [EnumMember] + CZK, + + /// + /// Denmark, Kroner. + /// + [EnumMember] + DKK, + + /// + /// Djibouti, Francs. + /// + [EnumMember] + DJF, + + /// + /// Dominican Republic, Pesos. + /// + [EnumMember] + DOP, + + /// + /// East Caribbean Dollars. + /// + [EnumMember] + XCD, + + /// + /// Egypt, Pounds. + /// + [EnumMember] + EGP, + + /// + /// El Salvador, Colones. + /// + [EnumMember] + SVC, + + /// + /// Estonia, Krooni. + /// + [EnumMember] + EEK, + + /// + /// Ethiopia, Birr. + /// + [EnumMember] + ETB, + + /// + /// Euro Member Countries, Euro. + /// + [EnumMember] + EUR, + + /// + /// Falkland Islands (Malvinas), Pounds. + /// + [EnumMember] + FKP, + + /// + /// Gambia, Dalasi. + /// + [EnumMember] + GMD, + + /// + /// Ghana, Cedis. + /// + [EnumMember] + GHC, + + /// + /// Gibraltar, Pounds. + /// + [EnumMember] + GIP, + + /// + /// Gold, Ounces. + /// + [EnumMember] + XAU, + + /// + /// Guatemala, Quetzales. + /// + [EnumMember] + GTQ, + + /// + /// Guinea, Francs. + /// + [EnumMember] + GNF, + + /// + /// Guyana, Dollars. + /// + [EnumMember] + GYD, + + /// + /// Haiti, Gourdes. + /// + [EnumMember] + HTG, + + /// + /// Honduras, Lempiras. + /// + [EnumMember] + HNL, + + /// + /// Hong Kong, Dollars. + /// + [EnumMember] + HKD, + + /// + /// Hungary, Forint. + /// + [EnumMember] + HUF, + + /// + /// Iceland, Kronur. + /// + [EnumMember] + ISK, + + /// + /// India, Rupees. + /// + [EnumMember] + INR, + + /// + /// Indonesia, Rupiahs. + /// + [EnumMember] + IDR, + + /// + /// Iraq, Dinars. + /// + [EnumMember] + IQD, + + /// + /// Israel, New Shekels. + /// + [EnumMember] + ILS, + + /// + /// Jamaica, Dollars. + /// + [EnumMember] + JMD, + + /// + /// Japan, Yen. + /// + [EnumMember] + JPY, + + /// + /// Jordan, Dinars. + /// + [EnumMember] + JOD, + + /// + /// Kazakstan, Tenge. + /// + [EnumMember] + KZT, + + /// + /// Kenya, Shillings. + /// + [EnumMember] + KES, + + /// + /// Korea (South), Won. + /// + [EnumMember] + KRW, + + /// + /// Kuwait, Dinars. + /// + [EnumMember] + KWD, + + /// + /// Laos, Kips. + /// + [EnumMember] + LAK, + + /// + /// Latvia, Lati. + /// + [EnumMember] + LVL, + + /// + /// Lebanon, Pounds. + /// + [EnumMember] + LBP, + + /// + /// Lesotho, Maloti. + /// + [EnumMember] + LSL, + + /// + /// Liberia, Dollars. + /// + [EnumMember] + LRD, + + /// + /// Libya, Dinars. + /// + [EnumMember] + LYD, + + /// + /// Lithuania, Litai. + /// + [EnumMember] + LTL, + + /// + /// Macau, Patacas. + /// + [EnumMember] + MOP, + + /// + /// Macedonia, Denars. + /// + [EnumMember] + MKD, + + /// + /// Malagasy, Franc. + /// + [EnumMember] + MGF, + + /// + /// Malawi, Kwachas. + /// + [EnumMember] + MWK, + + /// + /// Malaysia, Ringgits. + /// + [EnumMember] + MYR, + + /// + /// Maldives (Maldive Islands), Rufiyaa. + /// + [EnumMember] + MVR, + + /// + /// Malta, Liri. + /// + [EnumMember] + MTL, + + /// + /// Mauritania, Ouguiyas. + /// + [EnumMember] + MRO, + + /// + /// Mauritius, Rupees. + /// + [EnumMember] + MUR, + + /// + /// Mexico, Pesos. + /// + [EnumMember] + MXN, + + /// + /// Moldova, Lei. + /// + [EnumMember] + MDL, + + /// + /// Mongolia, Tugriks. + /// + [EnumMember] + MNT, + + /// + /// Morocco, Dirhams. + /// + [EnumMember] + MAD, + + /// + /// Mozambique, Meticais. + /// + [EnumMember] + MZM, + + /// + /// Myanmar (Burma), Kyats. + /// + [EnumMember] + MMK, + + /// + /// Namibia, Dollars. + /// + [EnumMember] + NAD, + + /// + /// Nepal, Nepal Rupees. + /// + [EnumMember] + NPR, + + /// + /// Netherlands Antilles, Guilders (also called Florins). + /// + [EnumMember] + ANG, + + /// + /// New Zealand, Dollars. + /// + [EnumMember] + NZD, + + /// + /// Nicaragua, Gold Cordobas. + /// + [EnumMember] + NIO, + + /// + /// Nigeria, Nairas. + /// + [EnumMember] + NGN, + + /// + /// Korea (North), Won. + /// + [EnumMember] + KPW, + + /// + /// Norway, Krone. + /// + [EnumMember] + NOK, + + /// + /// Oman, Rials. + /// + [EnumMember] + OMR, + + /// + /// Comptoirs Francais du Pacifique Francs. + /// + [EnumMember] + XPF, + + /// + /// Pakistan, Rupees. + /// + [EnumMember] + PKR, + + /// + /// Palladium Ounces. + /// + [EnumMember] + XPD, + + /// + /// Panama, Balboa. + /// + [EnumMember] + PAB, + + /// + /// Papua New Guinea, Kina. + /// + [EnumMember] + PGK, + + /// + /// Paraguay, Guarani. + /// + [EnumMember] + PYG, + + /// + /// Peru, Nuevos Soles. + /// + [EnumMember] + PEN, + + /// + /// Philippines, Pesos. + /// + [EnumMember] + PHP, + + /// + /// Platinum, Ounces. + /// + [EnumMember] + XPT, + + /// + /// Poland, Zlotych. + /// + [EnumMember] + PLN, + + /// + /// Qatar, Rials. + /// + [EnumMember] + QAR, + + /// + /// Russia, Abkhazia, South Ossetia, Russian rouble. + /// + [EnumMember] + RUB, + + /// + /// Samoa, Tala. + /// + [EnumMember] + WST, + + /// + /// Sao Tome and Principe, Dobras. + /// + [EnumMember] + STD, + + /// + /// Saudi Arabia, Riyals. + /// + [EnumMember] + SAR, + + /// + /// Seychelles, Rupees. + /// + [EnumMember] + SCR, + + /// + /// Sierra Leone, Leones. + /// + [EnumMember] + SLL, + + /// + /// Silver, Ounces. + /// + [EnumMember] + XAG, + + /// + /// Singapore, Dollars. + /// + [EnumMember] + SGD, + + /// + /// Slovakia, Koruny. + /// + [EnumMember] + SKK, + + /// + /// Slovenia, Tolars. + /// + [EnumMember] + SIT, + + /// + /// Solomon Islands, Dollars. + /// + [EnumMember] + SBD, + + /// + /// Somalia, Shillings. + /// + [EnumMember] + SOS, + + /// + /// South Africa, Rand. + /// + [EnumMember] + ZAR, + + /// + /// Sri Lanka, Rupees. + /// + [EnumMember] + LKR, + + /// + /// Saint Helena, Pounds. + /// + [EnumMember] + SHP, + + /// + /// Sudan, Dinars. + /// + [EnumMember] + SDD, + + /// + /// Surinamese dollar. + /// + [EnumMember] + SRD, + + /// + /// Swaziland, Emalangeni. + /// + [EnumMember] + SZL, + + /// + /// Sweden, Kronor. + /// + [EnumMember] + SEK, + + /// + /// Switzerland, Francs. + /// + [EnumMember] + CHF, + + /// + /// Syria, Pounds. + /// + [EnumMember] + SYP, + + /// + /// Taiwan, New Dollars. + /// + [EnumMember] + TWD, + + /// + /// Tanzania, Shillings. + /// + [EnumMember] + TZS, + + /// + /// Thailand, Baht. + /// + [EnumMember] + THB, + + /// + /// Tonga, Pa'anga. + /// + [EnumMember] + TOP, + + /// + /// Trinidad and Tobago, Dollars. + /// + [EnumMember] + TTD, /// - /// Currency type. + /// Tunisia, Dinars. /// - /// - /// The codes are set in accordance with the ISO 4217 Currency Codes. - /// - [Serializable] - [XmlType(Namespace = "http://www.webserviceX.NET/")] - [DataContract] - public enum CurrencyTypes - { -// ReSharper disable InconsistentNaming - /// - /// Afghanistan, Afghanis. - /// - [EnumMember] - AFA, - - /// - /// Turkmenistan, Turkmenistani manat. - /// - [EnumMember] - TMT, - - /// - /// Uzbekistan, Uzbekistan som. - /// - [EnumMember] - UZS, - - /// - /// Tajikistan, Somoni. - /// - [EnumMember] - TJS, - - /// - /// Armenia, Armenian dram. - /// - [EnumMember] - AMD, - - /// - /// International Monetary Fund, Special Drawing Rights. - /// - [EnumMember] - XDR, - - /// - /// Azerbaijan, Azerbaijani manat. - /// - [EnumMember] - AZN, - - /// - /// Belarus, Belarusian ruble. - /// - [EnumMember] - BYR, - - /// - /// Belarus, Belarusian ruble. - /// - [EnumMember] - BYN, - - /// - /// Romania, Romanian new leu. - /// - [EnumMember] - RON, - - /// - /// Bulgaria, Bulgarian lev. - /// - [EnumMember] - BGN, - - /// - /// Kyrgyzstan, Kyrgyzstani som. - /// - [EnumMember] - KGS, - - /// - /// Albania, Leke. - /// - [EnumMember] - ALL, - - /// - /// Algeria, Algeria Dinars. - /// - [EnumMember] - DZD, - - /// - /// Argentina, Pesos. - /// - [EnumMember] - ARS, - - /// - /// Aruba, Guilders (also called Florins). - /// - [EnumMember] - AWG, - - /// - /// Australia, Dollars. - /// - [EnumMember] - AUD, - - /// - /// Bahamas, Dollars. - /// - [EnumMember] - BSD, - - /// - /// Bahrain, Dinars. - /// - [EnumMember] - BHD, - - /// - /// Bangladesh, Taka. - /// - [EnumMember] - BDT, - - /// - /// Barbados, Dollars. - /// - [EnumMember] - BBD, - - /// - /// Belize, Dollars. - /// - [EnumMember] - BZD, - - /// - /// Bermuda, Dollars. - /// - [EnumMember] - BMD, - - /// - /// Bhutan, Ngultrum. - /// - [EnumMember] - BTN, - - /// - /// Bolivia, Bolivianos. - /// - [EnumMember] - BOB, - - /// - /// Botswana, Pulas. - /// - [EnumMember] - BWP, - - /// - /// Brazil, Brazil Real. - /// - [EnumMember] - BRL, - - /// - /// United Kingdom, Pounds sterling. - /// - [EnumMember] - GBP, - - /// - /// Brunei Darussalam, Dollars. - /// - [EnumMember] - BND, - - /// - /// Burundi, Francs. - /// - [EnumMember] - BIF, - - /// - /// Communaute Financiere Africaine BCEAO, Francs. - /// - [EnumMember] - XOF, - - /// - /// Communaute Financiere Africaine BEAC, Francs. - /// - [EnumMember] - XAF, - - /// - /// Cambodia, Riels. - /// - [EnumMember] - KHR, - - /// - /// Canada, Dollars. - /// - [EnumMember] - CAD, - - /// - /// Cape Verde, Escudos. - /// - [EnumMember] - CVE, - - /// - /// Cayman Islands, Dollars. - /// - [EnumMember] - KYD, - - /// - /// Chile, Pesos. - /// - [EnumMember] - CLP, - - /// - /// China, Yuan Renminbi. - /// - [EnumMember] - CNY, - - /// - /// China, offshore RMB. - /// - [EnumMember] - CNH, - - /// - /// Colombia, Pesos. - /// - [EnumMember] - COP, - - /// - /// Comoros, Francs. - /// - [EnumMember] - KMF, - - /// - /// Costa Rica, Colones. - /// - [EnumMember] - CRC, - - /// - /// Croatia, Kuna. - /// - [EnumMember] - HRK, - - /// - /// Cuba, Pesos. - /// - [EnumMember] - CUP, - - /// - /// Cyprus, Pounds. - /// - [EnumMember] - CYP, - - /// - /// Czech Republic, Koruny. - /// - [EnumMember] - CZK, - - /// - /// Denmark, Kroner. - /// - [EnumMember] - DKK, - - /// - /// Djibouti, Francs. - /// - [EnumMember] - DJF, - - /// - /// Dominican Republic, Pesos. - /// - [EnumMember] - DOP, - - /// - /// East Caribbean Dollars. - /// - [EnumMember] - XCD, - - /// - /// Egypt, Pounds. - /// - [EnumMember] - EGP, - - /// - /// El Salvador, Colones. - /// - [EnumMember] - SVC, - - /// - /// Estonia, Krooni. - /// - [EnumMember] - EEK, - - /// - /// Ethiopia, Birr. - /// - [EnumMember] - ETB, - - /// - /// Euro Member Countries, Euro. - /// - [EnumMember] - EUR, - - /// - /// Falkland Islands (Malvinas), Pounds. - /// - [EnumMember] - FKP, - - /// - /// Gambia, Dalasi. - /// - [EnumMember] - GMD, - - /// - /// Ghana, Cedis. - /// - [EnumMember] - GHC, - - /// - /// Gibraltar, Pounds. - /// - [EnumMember] - GIP, - - /// - /// Gold, Ounces. - /// - [EnumMember] - XAU, - - /// - /// Guatemala, Quetzales. - /// - [EnumMember] - GTQ, - - /// - /// Guinea, Francs. - /// - [EnumMember] - GNF, - - /// - /// Guyana, Dollars. - /// - [EnumMember] - GYD, - - /// - /// Haiti, Gourdes. - /// - [EnumMember] - HTG, - - /// - /// Honduras, Lempiras. - /// - [EnumMember] - HNL, - - /// - /// Hong Kong, Dollars. - /// - [EnumMember] - HKD, - - /// - /// Hungary, Forint. - /// - [EnumMember] - HUF, - - /// - /// Iceland, Kronur. - /// - [EnumMember] - ISK, - - /// - /// India, Rupees. - /// - [EnumMember] - INR, - - /// - /// Indonesia, Rupiahs. - /// - [EnumMember] - IDR, - - /// - /// Iraq, Dinars. - /// - [EnumMember] - IQD, - - /// - /// Israel, New Shekels. - /// - [EnumMember] - ILS, - - /// - /// Jamaica, Dollars. - /// - [EnumMember] - JMD, - - /// - /// Japan, Yen. - /// - [EnumMember] - JPY, - - /// - /// Jordan, Dinars. - /// - [EnumMember] - JOD, - - /// - /// Kazakstan, Tenge. - /// - [EnumMember] - KZT, - - /// - /// Kenya, Shillings. - /// - [EnumMember] - KES, - - /// - /// Korea (South), Won. - /// - [EnumMember] - KRW, - - /// - /// Kuwait, Dinars. - /// - [EnumMember] - KWD, - - /// - /// Laos, Kips. - /// - [EnumMember] - LAK, - - /// - /// Latvia, Lati. - /// - [EnumMember] - LVL, - - /// - /// Lebanon, Pounds. - /// - [EnumMember] - LBP, - - /// - /// Lesotho, Maloti. - /// - [EnumMember] - LSL, - - /// - /// Liberia, Dollars. - /// - [EnumMember] - LRD, - - /// - /// Libya, Dinars. - /// - [EnumMember] - LYD, - - /// - /// Lithuania, Litai. - /// - [EnumMember] - LTL, - - /// - /// Macau, Patacas. - /// - [EnumMember] - MOP, - - /// - /// Macedonia, Denars. - /// - [EnumMember] - MKD, - - /// - /// Malagasy, Franc. - /// - [EnumMember] - MGF, - - /// - /// Malawi, Kwachas. - /// - [EnumMember] - MWK, - - /// - /// Malaysia, Ringgits. - /// - [EnumMember] - MYR, - - /// - /// Maldives (Maldive Islands), Rufiyaa. - /// - [EnumMember] - MVR, - - /// - /// Malta, Liri. - /// - [EnumMember] - MTL, - - /// - /// Mauritania, Ouguiyas. - /// - [EnumMember] - MRO, - - /// - /// Mauritius, Rupees. - /// - [EnumMember] - MUR, - - /// - /// Mexico, Pesos. - /// - [EnumMember] - MXN, - - /// - /// Moldova, Lei. - /// - [EnumMember] - MDL, - - /// - /// Mongolia, Tugriks. - /// - [EnumMember] - MNT, - - /// - /// Morocco, Dirhams. - /// - [EnumMember] - MAD, - - /// - /// Mozambique, Meticais. - /// - [EnumMember] - MZM, - - /// - /// Myanmar (Burma), Kyats. - /// - [EnumMember] - MMK, - - /// - /// Namibia, Dollars. - /// - [EnumMember] - NAD, - - /// - /// Nepal, Nepal Rupees. - /// - [EnumMember] - NPR, - - /// - /// Netherlands Antilles, Guilders (also called Florins). - /// - [EnumMember] - ANG, - - /// - /// New Zealand, Dollars. - /// - [EnumMember] - NZD, - - /// - /// Nicaragua, Gold Cordobas. - /// - [EnumMember] - NIO, - - /// - /// Nigeria, Nairas. - /// - [EnumMember] - NGN, - - /// - /// Korea (North), Won. - /// - [EnumMember] - KPW, - - /// - /// Norway, Krone. - /// - [EnumMember] - NOK, - - /// - /// Oman, Rials. - /// - [EnumMember] - OMR, - - /// - /// Comptoirs Francais du Pacifique Francs. - /// - [EnumMember] - XPF, - - /// - /// Pakistan, Rupees. - /// - [EnumMember] - PKR, - - /// - /// Palladium Ounces. - /// - [EnumMember] - XPD, - - /// - /// Panama, Balboa. - /// - [EnumMember] - PAB, - - /// - /// Papua New Guinea, Kina. - /// - [EnumMember] - PGK, - - /// - /// Paraguay, Guarani. - /// - [EnumMember] - PYG, - - /// - /// Peru, Nuevos Soles. - /// - [EnumMember] - PEN, - - /// - /// Philippines, Pesos. - /// - [EnumMember] - PHP, - - /// - /// Platinum, Ounces. - /// - [EnumMember] - XPT, - - /// - /// Poland, Zlotych. - /// - [EnumMember] - PLN, - - /// - /// Qatar, Rials. - /// - [EnumMember] - QAR, - - /// - /// Russia, Abkhazia, South Ossetia, Russian rouble. - /// - [EnumMember] - RUB, - - /// - /// Samoa, Tala. - /// - [EnumMember] - WST, - - /// - /// Sao Tome and Principe, Dobras. - /// - [EnumMember] - STD, - - /// - /// Saudi Arabia, Riyals. - /// - [EnumMember] - SAR, - - /// - /// Seychelles, Rupees. - /// - [EnumMember] - SCR, - - /// - /// Sierra Leone, Leones. - /// - [EnumMember] - SLL, - - /// - /// Silver, Ounces. - /// - [EnumMember] - XAG, - - /// - /// Singapore, Dollars. - /// - [EnumMember] - SGD, - - /// - /// Slovakia, Koruny. - /// - [EnumMember] - SKK, - - /// - /// Slovenia, Tolars. - /// - [EnumMember] - SIT, - - /// - /// Solomon Islands, Dollars. - /// - [EnumMember] - SBD, - - /// - /// Somalia, Shillings. - /// - [EnumMember] - SOS, - - /// - /// South Africa, Rand. - /// - [EnumMember] - ZAR, - - /// - /// Sri Lanka, Rupees. - /// - [EnumMember] - LKR, - - /// - /// Saint Helena, Pounds. - /// - [EnumMember] - SHP, - - /// - /// Sudan, Dinars. - /// - [EnumMember] - SDD, - - /// - /// Surinamese dollar. - /// - [EnumMember] - SRD, - - /// - /// Swaziland, Emalangeni. - /// - [EnumMember] - SZL, - - /// - /// Sweden, Kronor. - /// - [EnumMember] - SEK, - - /// - /// Switzerland, Francs. - /// - [EnumMember] - CHF, - - /// - /// Syria, Pounds. - /// - [EnumMember] - SYP, - - /// - /// Taiwan, New Dollars. - /// - [EnumMember] - TWD, - - /// - /// Tanzania, Shillings. - /// - [EnumMember] - TZS, - - /// - /// Thailand, Baht. - /// - [EnumMember] - THB, - - /// - /// Tonga, Pa'anga. - /// - [EnumMember] - TOP, - - /// - /// Trinidad and Tobago, Dollars. - /// - [EnumMember] - TTD, - - /// - /// Tunisia, Dinars. - /// - [EnumMember] - TND, - - /// - /// Turkey, Liras. - /// - [EnumMember] - TRL, - - /// - /// United States of America, Dollars. - /// - [EnumMember] - USD, - - /// - /// United Arab Emirates, Dirhams. - /// - [EnumMember] - AED, - - /// - /// Uganda, Shillings. - /// - [EnumMember] - UGX, - - /// - /// Ukraine, Hryvnia. - /// - [EnumMember] - UAH, - - /// - /// Uruguay, Pesos. - /// - [EnumMember] - UYU, - - /// - /// Vanuatu, Vatu. - /// - [EnumMember] - VUV, - - /// - /// Venezuela, Bolivares. - /// - [EnumMember] - VEB, - - /// - /// Viet Nam, Dong. - /// - [EnumMember] - VND, - - /// - /// Yemen, Rials. - /// - [EnumMember] - YER, - - /// - /// Serbian dinar. - /// - [EnumMember] - CSD, - - /// - /// Zambia, Kwacha. - /// - [EnumMember] - ZMK, - - /// - /// Zimbabwe, Zimbabwe Dollars. - /// - [EnumMember] - ZWD, - - /// - /// Turkey, Northern Cyprus, Turkish lira. - /// - [EnumMember] - TRY, - - /// - /// Ven. - /// - [EnumMember] - XVN, - - /// - /// Bitcoin. - /// - [EnumMember] - [Crypto] - BTC, - - /// - /// United Kingdom, Pence sterling. - /// - [EnumMember] - GBX, - - /// - /// Ghana, Ghanaian Cedi. - /// - [EnumMember] - GHS, - - /// - /// China, offshore RMB. - /// - [EnumMember] - CNT, - - /// - /// Ethereum. - /// - [EnumMember] - [Crypto] - ETH, - - /// - /// Litecoin. - /// - [EnumMember] - [Crypto] - LTC, - - /// - /// Ethereum Classic. - /// - [EnumMember] - [Crypto] - ETC, - - /// - /// Tether USD. - /// - [EnumMember] - [Crypto] - USDT, - - /// - /// Zcash. - /// - [EnumMember] - [Crypto] - ZEC, - - /// - /// Monero. - /// - [EnumMember] - [Crypto] - XMR, - - /// - /// Cardano. - /// - [EnumMember] - [Crypto] - ADA, - - /// - /// IOTA. - /// - [EnumMember] - [Crypto] - MIOTA, - - /// - /// Ripple. - /// - [EnumMember] - [Crypto] - XRP, - - /// - /// Dash. - /// - [EnumMember] - [Crypto] - DASH, - - /// - /// EOS. - /// - [EnumMember] - [Crypto] - EOS, - - /// - /// Santiment. - /// - [EnumMember] - [Crypto] - SAN, - - /// - /// Omisego. - /// - [EnumMember] - [Crypto] - OMG, - - /// - /// Bitcoin Cash. - /// - [EnumMember] - [Crypto] - BCH, - - /// - /// Neo. - /// - [EnumMember] - [Crypto] - NEO, - - /// - /// Metaverse. - /// - [EnumMember] - [Crypto] - ETP, - - /// - /// Qtum. - /// - [EnumMember] - [Crypto] - QTUM, - - /// - /// Aventus. - /// - [EnumMember] - [Crypto] - AVT, - - /// - /// Eidoo. - /// - [EnumMember] - [Crypto] - EDO, - - /// - /// Datacoin. - /// - [EnumMember] - [Crypto] - DTC, - - /// - /// Bitcoin Gold. - /// - [EnumMember] - [Crypto] - BTG, - - /// - /// QASH. - /// - [EnumMember] - [Crypto] - QASH, - - /// - /// Yoyow. - /// - [EnumMember] - [Crypto] - YOYOW, - - /// - /// Golem. - /// - [EnumMember] - [Crypto] - GNT, - - /// - /// Status. - /// - [EnumMember] - [Crypto] - SNT, - - /// - /// Tether EUR. - /// - [EnumMember] - [Crypto] - EURT, - - /// - /// Basic Attention Token. - /// - [EnumMember] - [Crypto] - BAT, - - /// - /// MNA. - /// - [EnumMember] - [Crypto] - MNA, - - /// - /// FunFair. - /// - [EnumMember] - [Crypto] - FUN, - - /// - /// ZRX. - /// - [EnumMember] - [Crypto] - ZRX, - - /// - /// Time New Bank. - /// - [EnumMember] - [Crypto] - TNB, - - /// - /// Sparks. - /// - [EnumMember] - [Crypto] - SPK, - - /// - /// TRON. - /// - [EnumMember] - [Crypto] - TRX, - - /// - /// Ripio Credit Network. - /// - [EnumMember] - [Crypto] - RCN, - - /// - /// iExec. - /// - [EnumMember] - [Crypto] - RLC, - - /// - /// AidCoin. - /// - [EnumMember] - [Crypto] - AID, - - /// - /// SnowGem. - /// - [EnumMember] - [Crypto] - SNG, - - /// - /// Augur. - /// - [EnumMember] - [Crypto] - REP, - - /// - /// Aelf. - /// - [EnumMember] - [Crypto] - ELF, - - /// - /// South Africa, Rand. The Rand is subdivided into 100 cents. - /// - [EnumMember] - ZAC, - - /// - /// Deutsche Mark. - /// - [EnumMember] - DEM, - - /// - /// Luxembourgish franc. - /// - [EnumMember] - LUF, - - /// - /// Old Mexican Peso. - /// - [EnumMember] - MXP, - - /// - /// Malagasy ariary. - /// - [EnumMember] - MGA, - - /// - /// Angolan kwanza. - /// - [EnumMember] - AOA, - - /// - /// Fijian dollar. - /// - [EnumMember] - FJD, - - /// - /// Congolese franc. - /// - [EnumMember] - CDF, - - /// - /// Sudanese pound. - /// - [EnumMember] - SDG, - - /// - /// Mauritanian ouguiya. - /// - [EnumMember] - MRU, - - /// - /// Iranian rial. - /// - [EnumMember] - IRR, - - /// - /// Zambian kwacha. - /// - [EnumMember] - ZMW, - - /// - /// Georgian lari. - /// - [EnumMember] - GEL, - - /// - /// Sao Tome and Principe dobra. - /// - [EnumMember] - STN, - - /// - /// Venezuelan bolivar. - /// - [EnumMember] - VES, - - /// - /// South Sudanese pound. - /// - [EnumMember] - SSP, - - /// - /// Eritrean nakfa. - /// - [EnumMember] - ERN, - - /// - /// Bosnia-Herzegovina Convertible Marka. - /// - [EnumMember] - BAM, - - /// - /// Serbian dinar. - /// - [EnumMember] - RSD, - - /// - /// Rwandan franc. - /// - [EnumMember] - RWF, - - /// - /// Mozambican metical. - /// - [EnumMember] - MZN, - - /// - /// Mozambican metical. - /// - [EnumMember] - AFN, - - /// - /// USD Coin. - /// - [EnumMember] - [Crypto] - USDC, - - /// - /// Binance USD. - /// - [EnumMember] - [Crypto] - BUSD, - - /// - /// Dai. - /// - [EnumMember] - [Crypto] - DAI, - - /// - /// True USD. - /// - [EnumMember] - [Crypto] - TUSD, - - /// - /// Pax Dollar. - /// - [EnumMember] - [Crypto] - USDP, - - /// - /// Frax. - /// - [EnumMember] - [Crypto] - FRAX, - - /// - /// USDD. - /// - [EnumMember] - [Crypto] - USDD, - - /// - /// Gemini Dollar. - /// - [EnumMember] - [Crypto] - GUSD, - - /// - /// XSGD. - /// - [EnumMember] - [Crypto] - XSGD, - - /// - /// Pax Gold. - /// - [EnumMember] - [Crypto] - PAXG, - - /// - /// Tether Gold. - /// - [EnumMember] - [Crypto] - XAUT, - - /// - /// Liquity USD. - /// - [EnumMember] - [Crypto] - LUSD, - - /// - /// Micro Bitcoin. - /// - [EnumMember] - [Crypto] - MBC, - - /// - /// TerraUSD. - /// - [EnumMember] - [Crypto] - UST, - - /// - /// Brazilian Digital Token. - /// - [EnumMember] - [Crypto] - BRZ, - - /// - /// USDe. - /// - [EnumMember] - [Crypto] - USDE - // ReSharper restore InconsistentNaming - } + [EnumMember] + TND, + + /// + /// Turkey, Liras. + /// + [EnumMember] + TRL, + + /// + /// United States of America, Dollars. + /// + [EnumMember] + USD, + + /// + /// United Arab Emirates, Dirhams. + /// + [EnumMember] + AED, + + /// + /// Uganda, Shillings. + /// + [EnumMember] + UGX, + + /// + /// Ukraine, Hryvnia. + /// + [EnumMember] + UAH, + + /// + /// Uruguay, Pesos. + /// + [EnumMember] + UYU, + + /// + /// Vanuatu, Vatu. + /// + [EnumMember] + VUV, + + /// + /// Venezuela, Bolivares. + /// + [EnumMember] + VEB, + + /// + /// Viet Nam, Dong. + /// + [EnumMember] + VND, + + /// + /// Yemen, Rials. + /// + [EnumMember] + YER, + + /// + /// Serbian dinar. + /// + [EnumMember] + CSD, + + /// + /// Zambia, Kwacha. + /// + [EnumMember] + ZMK, + + /// + /// Zimbabwe, Zimbabwe Dollars. + /// + [EnumMember] + ZWD, + + /// + /// Turkey, Northern Cyprus, Turkish lira. + /// + [EnumMember] + TRY, + + /// + /// Ven. + /// + [EnumMember] + XVN, + + /// + /// Bitcoin. + /// + [EnumMember] + [Crypto] + BTC, + + /// + /// United Kingdom, Pence sterling. + /// + [EnumMember] + GBX, + + /// + /// Ghana, Ghanaian Cedi. + /// + [EnumMember] + GHS, + + /// + /// China, offshore RMB. + /// + [EnumMember] + CNT, + + /// + /// Ethereum. + /// + [EnumMember] + [Crypto] + ETH, + + /// + /// Litecoin. + /// + [EnumMember] + [Crypto] + LTC, + + /// + /// Ethereum Classic. + /// + [EnumMember] + [Crypto] + ETC, + + /// + /// Tether USD. + /// + [EnumMember] + [Crypto] + USDT, + + /// + /// Zcash. + /// + [EnumMember] + [Crypto] + ZEC, + + /// + /// Monero. + /// + [EnumMember] + [Crypto] + XMR, + + /// + /// Cardano. + /// + [EnumMember] + [Crypto] + ADA, + + /// + /// IOTA. + /// + [EnumMember] + [Crypto] + MIOTA, + + /// + /// Ripple. + /// + [EnumMember] + [Crypto] + XRP, + + /// + /// Dash. + /// + [EnumMember] + [Crypto] + DASH, + + /// + /// EOS. + /// + [EnumMember] + [Crypto] + EOS, + + /// + /// Santiment. + /// + [EnumMember] + [Crypto] + SAN, + + /// + /// Omisego. + /// + [EnumMember] + [Crypto] + OMG, + + /// + /// Bitcoin Cash. + /// + [EnumMember] + [Crypto] + BCH, + + /// + /// Neo. + /// + [EnumMember] + [Crypto] + NEO, + + /// + /// Metaverse. + /// + [EnumMember] + [Crypto] + ETP, + + /// + /// Qtum. + /// + [EnumMember] + [Crypto] + QTUM, + + /// + /// Aventus. + /// + [EnumMember] + [Crypto] + AVT, + + /// + /// Eidoo. + /// + [EnumMember] + [Crypto] + EDO, + + /// + /// Datacoin. + /// + [EnumMember] + [Crypto] + DTC, + + /// + /// Bitcoin Gold. + /// + [EnumMember] + [Crypto] + BTG, + + /// + /// QASH. + /// + [EnumMember] + [Crypto] + QASH, + + /// + /// Yoyow. + /// + [EnumMember] + [Crypto] + YOYOW, + + /// + /// Golem. + /// + [EnumMember] + [Crypto] + GNT, + + /// + /// Status. + /// + [EnumMember] + [Crypto] + SNT, + + /// + /// Tether EUR. + /// + [EnumMember] + [Crypto] + EURT, + + /// + /// Basic Attention Token. + /// + [EnumMember] + [Crypto] + BAT, + + /// + /// MNA. + /// + [EnumMember] + [Crypto] + MNA, + + /// + /// FunFair. + /// + [EnumMember] + [Crypto] + FUN, + + /// + /// ZRX. + /// + [EnumMember] + [Crypto] + ZRX, + + /// + /// Time New Bank. + /// + [EnumMember] + [Crypto] + TNB, + + /// + /// Sparks. + /// + [EnumMember] + [Crypto] + SPK, + + /// + /// TRON. + /// + [EnumMember] + [Crypto] + TRX, + + /// + /// Ripio Credit Network. + /// + [EnumMember] + [Crypto] + RCN, + + /// + /// iExec. + /// + [EnumMember] + [Crypto] + RLC, + + /// + /// AidCoin. + /// + [EnumMember] + [Crypto] + AID, + + /// + /// SnowGem. + /// + [EnumMember] + [Crypto] + SNG, + + /// + /// Augur. + /// + [EnumMember] + [Crypto] + REP, + + /// + /// Aelf. + /// + [EnumMember] + [Crypto] + ELF, + + /// + /// South Africa, Rand. The Rand is subdivided into 100 cents. + /// + [EnumMember] + ZAC, + + /// + /// Deutsche Mark. + /// + [EnumMember] + DEM, + + /// + /// Luxembourgish franc. + /// + [EnumMember] + LUF, + + /// + /// Old Mexican Peso. + /// + [EnumMember] + MXP, + + /// + /// Malagasy ariary. + /// + [EnumMember] + MGA, + + /// + /// Angolan kwanza. + /// + [EnumMember] + AOA, + + /// + /// Fijian dollar. + /// + [EnumMember] + FJD, + + /// + /// Congolese franc. + /// + [EnumMember] + CDF, + + /// + /// Sudanese pound. + /// + [EnumMember] + SDG, + + /// + /// Mauritanian ouguiya. + /// + [EnumMember] + MRU, + + /// + /// Iranian rial. + /// + [EnumMember] + IRR, + + /// + /// Zambian kwacha. + /// + [EnumMember] + ZMW, + + /// + /// Georgian lari. + /// + [EnumMember] + GEL, + + /// + /// Sao Tome and Principe dobra. + /// + [EnumMember] + STN, + + /// + /// Venezuelan bolivar. + /// + [EnumMember] + VES, + + /// + /// South Sudanese pound. + /// + [EnumMember] + SSP, + + /// + /// Eritrean nakfa. + /// + [EnumMember] + ERN, + + /// + /// Bosnia-Herzegovina Convertible Marka. + /// + [EnumMember] + BAM, + + /// + /// Serbian dinar. + /// + [EnumMember] + RSD, + + /// + /// Rwandan franc. + /// + [EnumMember] + RWF, + + /// + /// Mozambican metical. + /// + [EnumMember] + MZN, + + /// + /// Mozambican metical. + /// + [EnumMember] + AFN, + + /// + /// USD Coin. + /// + [EnumMember] + [Crypto] + USDC, + + /// + /// Binance USD. + /// + [EnumMember] + [Crypto] + BUSD, + + /// + /// Dai. + /// + [EnumMember] + [Crypto] + DAI, + + /// + /// True USD. + /// + [EnumMember] + [Crypto] + TUSD, + + /// + /// Pax Dollar. + /// + [EnumMember] + [Crypto] + USDP, + + /// + /// Frax. + /// + [EnumMember] + [Crypto] + FRAX, + + /// + /// USDD. + /// + [EnumMember] + [Crypto] + USDD, + + /// + /// Gemini Dollar. + /// + [EnumMember] + [Crypto] + GUSD, + + /// + /// XSGD. + /// + [EnumMember] + [Crypto] + XSGD, + + /// + /// Pax Gold. + /// + [EnumMember] + [Crypto] + PAXG, + + /// + /// Tether Gold. + /// + [EnumMember] + [Crypto] + XAUT, + + /// + /// Liquity USD. + /// + [EnumMember] + [Crypto] + LUSD, + + /// + /// Micro Bitcoin. + /// + [EnumMember] + [Crypto] + MBC, + + /// + /// TerraUSD. + /// + [EnumMember] + [Crypto] + UST, + + /// + /// Brazilian Digital Token. + /// + [EnumMember] + [Crypto] + BRZ, + + /// + /// USDe. + /// + [EnumMember] + [Crypto] + USDE + // ReSharper restore InconsistentNaming } \ No newline at end of file diff --git a/Common/DelegateHelper.cs b/Common/DelegateHelper.cs index 2878c212..fedc6caa 100644 --- a/Common/DelegateHelper.cs +++ b/Common/DelegateHelper.cs @@ -1,187 +1,186 @@ -namespace Ecng.Common +namespace Ecng.Common; + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Reflection; +using System.Linq; + +/// +/// Provides helper methods for working with delegates. +/// +public static class DelegateHelper { - using System; - using System.Collections.Generic; - using System.ComponentModel; - using System.Reflection; - using System.Linq; - /// - /// Provides helper methods for working with delegates. + /// Executes the specified action and handles any exception using the provided error action. /// - public static class DelegateHelper + /// The action to execute. + /// The action to handle exceptions. + /// Thrown when action or error is null. + public static void Do(this Action action, Action error) { - /// - /// Executes the specified action and handles any exception using the provided error action. - /// - /// The action to execute. - /// The action to handle exceptions. - /// Thrown when action or error is null. - public static void Do(this Action action, Action error) + if (action is null) + throw new ArgumentNullException(nameof(action)); + + if (error is null) + throw new ArgumentNullException(nameof(error)); + + try + { + action(); + } + catch (Exception ex) { - if (action is null) - throw new ArgumentNullException(nameof(action)); + error(ex); + } + } - if (error is null) - throw new ArgumentNullException(nameof(error)); + /// + /// Executes the specified action asynchronously and handles any exception using the provided error action. + /// + /// The action to execute asynchronously. + /// The action to handle exceptions. + /// Thrown when action or error is null. + public static void DoAsync(this Action action, Action error) + { + if (action is null) + throw new ArgumentNullException(nameof(action)); + + if (error is null) + throw new ArgumentNullException(nameof(error)); + Do(() => action.BeginInvoke(result => + { try { - action(); + action.EndInvoke(result); } catch (Exception ex) { error(ex); } - } - - /// - /// Executes the specified action asynchronously and handles any exception using the provided error action. - /// - /// The action to execute asynchronously. - /// The action to handle exceptions. - /// Thrown when action or error is null. - public static void DoAsync(this Action action, Action error) - { - if (action is null) - throw new ArgumentNullException(nameof(action)); + }, null), error); + } - if (error is null) - throw new ArgumentNullException(nameof(error)); + //// The following methods are commented out. + ///// + ///// Casts a delegate to an EventHandler of the specified event argument type. + ///// + ///// The type of the event arguments. + ///// The delegate to cast. + ///// An EventHandler of type TArgs if the cast is successful; otherwise, null. + //public static EventHandler Cast(this Delegate handler) + // where TArgs : EventArgs + //{ + // if (handler is null) + // return null; + + // dynamic h = handler; + + // // Resharper shows wrong hint + // // DO NOT convert to method GROUP + // return (sender, e) => h(sender, e); + //} + + ///// + ///// Casts an EventHandler to EventHandler of EventArgs. + ///// + ///// The EventHandler to cast. + ///// An EventHandler of EventArgs if the cast is successful; otherwise, null. + //public static EventHandler Cast(this EventHandler handler) + //{ + // return handler.Cast(); + //} - Do(() => action.BeginInvoke(result => - { - try - { - action.EndInvoke(result); - } - catch (Exception ex) - { - error(ex); - } - }, null), error); - } - - //// The following methods are commented out. - ///// - ///// Casts a delegate to an EventHandler of the specified event argument type. - ///// - ///// The type of the event arguments. - ///// The delegate to cast. - ///// An EventHandler of type TArgs if the cast is successful; otherwise, null. - //public static EventHandler Cast(this Delegate handler) - // where TArgs : EventArgs - //{ - // if (handler is null) - // return null; - - // dynamic h = handler; - - // // Resharper shows wrong hint - // // DO NOT convert to method GROUP - // return (sender, e) => h(sender, e); - //} - - ///// - ///// Casts an EventHandler to EventHandler of EventArgs. - ///// - ///// The EventHandler to cast. - ///// An EventHandler of EventArgs if the cast is successful; otherwise, null. - //public static EventHandler Cast(this EventHandler handler) - //{ - // return handler.Cast(); - //} - - /// - /// Invokes the PropertyChangedEventHandler with the specified sender and property name. - /// - /// The PropertyChangedEventHandler to invoke. - /// The sender object. - /// The name of the property that changed. - public static void Invoke(this PropertyChangedEventHandler handler, object sender, string name) - { - handler(sender, new PropertyChangedEventArgs(name)); - } + /// + /// Invokes the PropertyChangedEventHandler with the specified sender and property name. + /// + /// The PropertyChangedEventHandler to invoke. + /// The sender object. + /// The name of the property that changed. + public static void Invoke(this PropertyChangedEventHandler handler, object sender, string name) + { + handler(sender, new PropertyChangedEventArgs(name)); + } - /// - /// Invokes the PropertyChangingEventHandler with the specified sender and property name. - /// - /// The PropertyChangingEventHandler to invoke. - /// The sender object. - /// The name of the property that is changing. - public static void Invoke(this PropertyChangingEventHandler handler, object sender, string name) - { - handler(sender, new PropertyChangingEventArgs(name)); - } + /// + /// Invokes the PropertyChangingEventHandler with the specified sender and property name. + /// + /// The PropertyChangingEventHandler to invoke. + /// The sender object. + /// The name of the property that is changing. + public static void Invoke(this PropertyChangingEventHandler handler, object sender, string name) + { + handler(sender, new PropertyChangingEventArgs(name)); + } - /// - /// Creates a delegate of type TDelegate for the specified method. - /// - /// The type of delegate to create. - /// The method information. - /// A delegate of type TDelegate. - public static TDelegate CreateDelegate(this MethodInfo method) - { - return Delegate.CreateDelegate(typeof(TDelegate), method, true).To(); - } + /// + /// Creates a delegate of type TDelegate for the specified method. + /// + /// The type of delegate to create. + /// The method information. + /// A delegate of type TDelegate. + public static TDelegate CreateDelegate(this MethodInfo method) + { + return Delegate.CreateDelegate(typeof(TDelegate), method, true).To(); + } - /// - /// Creates a delegate of type TDelegate for the specified method and instance. - /// - /// The type of the instance. - /// The type of delegate to create. - /// The method information. - /// The instance on which the method is invoked. - /// A delegate of type TDelegate bound to the specified instance. - public static TDelegate CreateDelegate(this MethodInfo method, TInstance instance) - { - return Delegate.CreateDelegate(typeof(TDelegate), instance, method, true).To(); - } + /// + /// Creates a delegate of type TDelegate for the specified method and instance. + /// + /// The type of the instance. + /// The type of delegate to create. + /// The method information. + /// The instance on which the method is invoked. + /// A delegate of type TDelegate bound to the specified instance. + public static TDelegate CreateDelegate(this MethodInfo method, TInstance instance) + { + return Delegate.CreateDelegate(typeof(TDelegate), instance, method, true).To(); + } - /// - /// Combines two delegates of the same type. - /// - /// The type of the delegate. - /// The source delegate. - /// The delegate to combine with the source delegate. - /// A combined delegate of type TDelegate. - public static TDelegate AddDelegate(this TDelegate source, TDelegate value) - { - return Delegate.Combine(source.To(), value.To()).To(); - } + /// + /// Combines two delegates of the same type. + /// + /// The type of the delegate. + /// The source delegate. + /// The delegate to combine with the source delegate. + /// A combined delegate of type TDelegate. + public static TDelegate AddDelegate(this TDelegate source, TDelegate value) + { + return Delegate.Combine(source.To(), value.To()).To(); + } - /// - /// Removes the specified delegate from the source delegate. - /// - /// The type of the delegate. - /// The source delegate. - /// The delegate to remove from the source. - /// The resulting delegate of type TDelegate after removal. - public static TDelegate RemoveDelegate(this TDelegate source, TDelegate value) - { - return Delegate.Remove(source.To(), value.To()).To(); - } + /// + /// Removes the specified delegate from the source delegate. + /// + /// The type of the delegate. + /// The source delegate. + /// The delegate to remove from the source. + /// The resulting delegate of type TDelegate after removal. + public static TDelegate RemoveDelegate(this TDelegate source, TDelegate value) + { + return Delegate.Remove(source.To(), value.To()).To(); + } - /// - /// Removes all invocation entries from the delegate. - /// - /// The type of the delegate. - /// The delegate from which all delegates are removed. - public static void RemoveAllDelegates(this TDelegate source) - { - foreach (var item in source.GetInvocationList()) - source.RemoveDelegate(item); - } + /// + /// Removes all invocation entries from the delegate. + /// + /// The type of the delegate. + /// The delegate from which all delegates are removed. + public static void RemoveAllDelegates(this TDelegate source) + { + foreach (var item in source.GetInvocationList()) + source.RemoveDelegate(item); + } - /// - /// Returns an enumerable collection of delegates from the delegate's invocation list. - /// - /// The type of the delegate. - /// The delegate whose invocation list is retrieved. - /// An IEnumerable of TDelegate representing the invocation list. - public static IEnumerable GetInvocationList(this TDelegate @delegate) - { - return @delegate.To()?.GetInvocationList().Cast() ?? []; - } + /// + /// Returns an enumerable collection of delegates from the delegate's invocation list. + /// + /// The type of the delegate. + /// The delegate whose invocation list is retrieved. + /// An IEnumerable of TDelegate representing the invocation list. + public static IEnumerable GetInvocationList(this TDelegate @delegate) + { + return @delegate.To()?.GetInvocationList().Cast() ?? []; } } \ No newline at end of file diff --git a/Common/Disposable.cs b/Common/Disposable.cs index afbe8917..f4813c8e 100644 --- a/Common/Disposable.cs +++ b/Common/Disposable.cs @@ -1,148 +1,147 @@ -namespace Ecng.Common +namespace Ecng.Common; + +#region Using Directives + +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Xml.Serialization; + +#endregion + +/// +/// Provides a base class for implementing the dispose pattern. +/// This class helps manage the disposal of managed and native resources. +/// +[Serializable] +public abstract class Disposable : IDisposable { - #region Using Directives + private readonly SyncObject _lock = new(); + + enum DisposeState : byte + { + None, + Disposing, + Disposed + } + + #region IsDisposed - using System; - using System.ComponentModel; - using System.Diagnostics; - using System.Xml.Serialization; + private DisposeState _state = DisposeState.None; + + /// + /// Gets a value indicating whether this instance is disposed. + /// + /// + /// true if this instance is disposed; otherwise, false. + /// + [XmlIgnore] + [Browsable(false)] + public bool IsDisposed => _state == DisposeState.Disposed; + + /// + /// Gets a value indicating whether the dispose process has been started. + /// + /// + /// true if the dispose process has been initiated; otherwise, false. + /// + [XmlIgnore] + [Browsable(false)] + public bool IsDisposeStarted => _state > DisposeState.None; #endregion /// - /// Provides a base class for implementing the dispose pattern. - /// This class helps manage the disposal of managed and native resources. + /// Occurs when the object has been disposed. /// - [Serializable] - public abstract class Disposable : IDisposable - { - private readonly SyncObject _lock = new(); + public event Action Disposed; - enum DisposeState : byte - { - None, - Disposing, - Disposed - } + #region IDisposable Members - #region IsDisposed - - private DisposeState _state = DisposeState.None; - - /// - /// Gets a value indicating whether this instance is disposed. - /// - /// - /// true if this instance is disposed; otherwise, false. - /// - [XmlIgnore] - [Browsable(false)] - public bool IsDisposed => _state == DisposeState.Disposed; - - /// - /// Gets a value indicating whether the dispose process has been started. - /// - /// - /// true if the dispose process has been initiated; otherwise, false. - /// - [XmlIgnore] - [Browsable(false)] - public bool IsDisposeStarted => _state > DisposeState.None; - - #endregion - - /// - /// Occurs when the object has been disposed. - /// - public event Action Disposed; - - #region IDisposable Members - - /// - /// Performs tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public virtual void Dispose() + /// + /// Performs tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public virtual void Dispose() + { + lock (_lock) { - lock (_lock) - { - if (IsDisposeStarted) - return; - - _state = DisposeState.Disposing; - } - - try - { - DisposeManaged(); - DisposeNative(); - } - finally - { - _state = DisposeState.Disposed; - Disposed?.Invoke(); - GC.SuppressFinalize(this); - } - } + if (IsDisposeStarted) + return; - #endregion + _state = DisposeState.Disposing; + } - /// - /// Disposes the managed resources. - /// Override this method to add custom clean up of managed resources. - /// - protected virtual void DisposeManaged() + try { + DisposeManaged(); + DisposeNative(); } - - /// - /// Disposes the native resources. - /// Override this method to add custom clean up of native resources. - /// - protected virtual void DisposeNative() + finally { + _state = DisposeState.Disposed; + Disposed?.Invoke(); + GC.SuppressFinalize(this); } + } - /// - /// Throws an exception if the dispose process has already been started. - /// - /// Thrown if the dispose process has already been initiated. - protected void ThrowIfDisposeStarted() - { - ThrowIfDisposed(); + #endregion - if (IsDisposeStarted) - throw new ObjectDisposedException(GetType().Name + " has started dispose process"); - } + /// + /// Disposes the managed resources. + /// Override this method to add custom clean up of managed resources. + /// + protected virtual void DisposeManaged() + { + } - /// - /// Throws an exception if the object is already disposed. - /// - /// Thrown if the object is already disposed. - protected void ThrowIfDisposed() - { - if (IsDisposed) - throw new ObjectDisposedException(GetType().Name); - } + /// + /// Disposes the native resources. + /// Override this method to add custom clean up of native resources. + /// + protected virtual void DisposeNative() + { + } - #region Finalize + /// + /// Throws an exception if the dispose process has already been started. + /// + /// Thrown if the dispose process has already been initiated. + protected void ThrowIfDisposeStarted() + { + ThrowIfDisposed(); + + if (IsDisposeStarted) + throw new ObjectDisposedException(GetType().Name + " has started dispose process"); + } - /// - /// Releases unmanaged resources and performs other cleanup operations before the - /// object is reclaimed by garbage collection. - /// - ~Disposable() + /// + /// Throws an exception if the object is already disposed. + /// + /// Thrown if the object is already disposed. + protected void ThrowIfDisposed() + { + if (IsDisposed) + throw new ObjectDisposedException(GetType().Name); + } + + #region Finalize + + /// + /// Releases unmanaged resources and performs other cleanup operations before the + /// object is reclaimed by garbage collection. + /// + ~Disposable() + { + // http://stackoverflow.com/a/9903121 + try { - // http://stackoverflow.com/a/9903121 - try - { - DisposeNative(); - } - catch (Exception ex) - { - Trace.WriteLine(ex); - } + DisposeNative(); + } + catch (Exception ex) + { + Trace.WriteLine(ex); } - - #endregion } + + #endregion } diff --git a/Common/DisposableHelper.cs b/Common/DisposableHelper.cs index 6393987c..9385a956 100644 --- a/Common/DisposableHelper.cs +++ b/Common/DisposableHelper.cs @@ -1,67 +1,66 @@ -namespace Ecng.Common -{ - using System; - using System.Collections.Generic; - - /// - /// Provides helper methods for creating and managing disposable objects. - /// - public static class DisposableHelper - { - private sealed class DisposableByAction : Disposable - { - private readonly T _unmanagedData; - private readonly Action _disposeAction; +namespace Ecng.Common; - /// - /// Initializes a new instance of the class with the specified unmanaged data and disposal action. - /// - /// The unmanaged data to dispose. - /// The action that disposes the unmanaged data. - /// Thrown when unmanagedData or disposeAction is null. - public DisposableByAction(T unmanagedData, Action disposeAction) - { - if (unmanagedData.IsNull()) - throw new ArgumentNullException(nameof(unmanagedData)); +using System; +using System.Collections.Generic; - _unmanagedData = unmanagedData; - _disposeAction = disposeAction ?? throw new ArgumentNullException(nameof(disposeAction)); - } - - /// - /// Disposes the managed resources. - /// - protected override void DisposeManaged() - { - _disposeAction(_unmanagedData); - base.DisposeManaged(); - } - } +/// +/// Provides helper methods for creating and managing disposable objects. +/// +public static class DisposableHelper +{ + private sealed class DisposableByAction : Disposable + { + private readonly T _unmanagedData; + private readonly Action _disposeAction; /// - /// Disposes all IDisposable objects in the enumerable. + /// Initializes a new instance of the class with the specified unmanaged data and disposal action. /// - /// The enumerable of disposable objects. - /// Thrown when disposables is null. - public static void DisposeAll(this IEnumerable disposables) + /// The unmanaged data to dispose. + /// The action that disposes the unmanaged data. + /// Thrown when unmanagedData or disposeAction is null. + public DisposableByAction(T unmanagedData, Action disposeAction) { - if (disposables is null) - throw new ArgumentNullException(nameof(disposables)); + if (unmanagedData.IsNull()) + throw new ArgumentNullException(nameof(unmanagedData)); - foreach (var disp in disposables) - disp.Dispose(); + _unmanagedData = unmanagedData; + _disposeAction = disposeAction ?? throw new ArgumentNullException(nameof(disposeAction)); } /// - /// Creates a disposable object that performs the specified disposal action on the provided unmanaged data. + /// Disposes the managed resources. /// - /// The type of the unmanaged data. - /// The unmanaged data to be disposed. - /// The action to execute during disposal. - /// A disposable object that calls the disposal action. - public static Disposable MakeDisposable(this T unmanagedData, Action disposeAction) + protected override void DisposeManaged() { - return new DisposableByAction(unmanagedData, disposeAction); + _disposeAction(_unmanagedData); + base.DisposeManaged(); } } + + /// + /// Disposes all IDisposable objects in the enumerable. + /// + /// The enumerable of disposable objects. + /// Thrown when disposables is null. + public static void DisposeAll(this IEnumerable disposables) + { + if (disposables is null) + throw new ArgumentNullException(nameof(disposables)); + + foreach (var disp in disposables) + disp.Dispose(); + } + + /// + /// Creates a disposable object that performs the specified disposal action on the provided unmanaged data. + /// + /// The type of the unmanaged data. + /// The unmanaged data to be disposed. + /// The action to execute during disposal. + /// A disposable object that calls the disposal action. + public static Disposable MakeDisposable(this T unmanagedData, Action disposeAction) + { + return new DisposableByAction(unmanagedData, disposeAction); + } } \ No newline at end of file diff --git a/Common/Do.cs b/Common/Do.cs index 05b349b6..aa951162 100644 --- a/Common/Do.cs +++ b/Common/Do.cs @@ -1,27 +1,26 @@ -namespace Ecng.Common -{ - using System; - using System.Globalization; +namespace Ecng.Common; + +using System; +using System.Globalization; +/// +/// Provides utility methods to execute functions and actions under the invariant culture. +/// +public static class Do +{ /// - /// Provides utility methods to execute functions and actions under the invariant culture. + /// Executes the specified function under the invariant culture and returns its result. /// - public static class Do - { - /// - /// Executes the specified function under the invariant culture and returns its result. - /// - /// The type of the result produced by the function. - /// The function to execute. - /// The result of executing the function. - public static T Invariant(Func func) - => CultureInfo.InvariantCulture.DoInCulture(func); + /// The type of the result produced by the function. + /// The function to execute. + /// The result of executing the function. + public static T Invariant(Func func) + => CultureInfo.InvariantCulture.DoInCulture(func); - /// - /// Executes the specified action under the invariant culture. - /// - /// The action to execute. - public static void Invariant(Action action) - => CultureInfo.InvariantCulture.DoInCulture(action); - } + /// + /// Executes the specified action under the invariant culture. + /// + /// The action to execute. + public static void Invariant(Action action) + => CultureInfo.InvariantCulture.DoInCulture(action); } \ No newline at end of file diff --git a/Common/DumpableStream.cs b/Common/DumpableStream.cs index 7dc668bd..2274f5b5 100644 --- a/Common/DumpableStream.cs +++ b/Common/DumpableStream.cs @@ -1,154 +1,153 @@ -namespace Ecng.Common +namespace Ecng.Common; + +using System; +using System.IO; + +/// +/// Represents a stream wrapper that records all data read from and written to the underlying stream. +/// +/// The underlying stream to wrap. +public class DumpableStream(Stream underlying) : Stream { - using System; - using System.IO; + private readonly Stream _underlying = underlying ?? throw new ArgumentNullException(nameof(underlying)); + + /// + /// Retrieves and clears the dump of data read from the underlying stream. + /// + /// A byte array containing the dumped read data. + public byte[] GetReadDump() + { + return GetDump(ReadDump); + } + + /// + /// Retrieves and clears the dump of data written to the underlying stream. + /// + /// A byte array containing the dumped write data. + public byte[] GetWriteDump() + { + return GetDump(WriteDump); + } + + private static byte[] GetDump(AllocationArray dump) + { + var buffer = new byte[dump.Count]; + Array.Copy(dump.Buffer, 0, buffer, 0, buffer.Length); + dump.Count = 0; + return buffer; + } + + /// + /// Gets the allocation array that collects data read from the underlying stream. + /// + public AllocationArray ReadDump { get; } = []; + + /// + /// Gets the allocation array that collects data written to the underlying stream. + /// + public AllocationArray WriteDump { get; } = []; + + /// + /// When overridden in a derived class, clears all buffers for this stream and causes any buffered data to be written to the underlying device. + /// + /// An I/O error occurs. + public override void Flush() + { + _underlying.Flush(); + } + + /// + /// When overridden in a derived class, sets the position within the current stream. + /// + /// A byte offset relative to the parameter. + /// A value of type indicating the reference point used to obtain the new position. + /// The new position within the current stream. + /// An I/O error occurs. + /// The stream does not support seeking. + /// Methods were called after the stream was closed. + public override long Seek(long offset, SeekOrigin origin) + { + return _underlying.Seek(offset, origin); + } + + /// + /// When overridden in a derived class, sets the length of the current stream. + /// + /// The desired length of the current stream in bytes. + /// An I/O error occurs. + /// The stream does not support both writing and seeking. + /// Methods were called after the stream was closed. + public override void SetLength(long value) + { + _underlying.SetLength(value); + } + + /// + /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. + /// + /// An array of bytes. When this method returns, the buffer contains the specified byte array with the values between and ( + - 1) replaced by the bytes read from the current source. + /// The zero-based byte offset in at which to begin storing the data read from the current stream. + /// The maximum number of bytes to be read from the current stream. + /// The total number of bytes read into the buffer. + /// The sum of and is larger than the buffer length. + /// is null. + /// or is negative. + /// An I/O error occurs. + /// The stream does not support reading. + /// Methods were called after the stream was closed. + public override int Read(byte[] buffer, int offset, int count) + { + var read = _underlying.Read(buffer, offset, count); + + if (read > 0) + ReadDump.Add(buffer, offset, read); + + return read; + } + + /// + /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. + /// + /// An array of bytes. This method copies bytes from to the current stream. + /// The zero-based byte offset in at which to begin copying bytes to the current stream. + /// The number of bytes to be written to the current stream. + public override void Write(byte[] buffer, int offset, int count) + { + _underlying.Write(buffer, offset, count); + WriteDump.Add(buffer, offset, count); + } + + /// + /// Gets a value indicating whether the current stream supports reading. + /// + public override bool CanRead => _underlying.CanRead; + + /// + /// Gets a value indicating whether the current stream supports seeking. + /// + public override bool CanSeek => _underlying.CanSeek; + + /// + /// Gets a value indicating whether the current stream supports writing. + /// + public override bool CanWrite => _underlying.CanWrite; + + /// + /// Gets the length in bytes of the stream. + /// + /// A class derived from Stream does not support seeking. + /// Methods were called after the stream was closed. + public override long Length => _underlying.Length; /// - /// Represents a stream wrapper that records all data read from and written to the underlying stream. + /// Gets or sets the position within the current stream. /// - /// The underlying stream to wrap. - public class DumpableStream(Stream underlying) : Stream + /// An I/O error occurs. + /// The stream does not support seeking. + /// Methods were called after the stream was closed. + public override long Position { - private readonly Stream _underlying = underlying ?? throw new ArgumentNullException(nameof(underlying)); - - /// - /// Retrieves and clears the dump of data read from the underlying stream. - /// - /// A byte array containing the dumped read data. - public byte[] GetReadDump() - { - return GetDump(ReadDump); - } - - /// - /// Retrieves and clears the dump of data written to the underlying stream. - /// - /// A byte array containing the dumped write data. - public byte[] GetWriteDump() - { - return GetDump(WriteDump); - } - - private static byte[] GetDump(AllocationArray dump) - { - var buffer = new byte[dump.Count]; - Array.Copy(dump.Buffer, 0, buffer, 0, buffer.Length); - dump.Count = 0; - return buffer; - } - - /// - /// Gets the allocation array that collects data read from the underlying stream. - /// - public AllocationArray ReadDump { get; } = []; - - /// - /// Gets the allocation array that collects data written to the underlying stream. - /// - public AllocationArray WriteDump { get; } = []; - - /// - /// When overridden in a derived class, clears all buffers for this stream and causes any buffered data to be written to the underlying device. - /// - /// An I/O error occurs. - public override void Flush() - { - _underlying.Flush(); - } - - /// - /// When overridden in a derived class, sets the position within the current stream. - /// - /// A byte offset relative to the parameter. - /// A value of type indicating the reference point used to obtain the new position. - /// The new position within the current stream. - /// An I/O error occurs. - /// The stream does not support seeking. - /// Methods were called after the stream was closed. - public override long Seek(long offset, SeekOrigin origin) - { - return _underlying.Seek(offset, origin); - } - - /// - /// When overridden in a derived class, sets the length of the current stream. - /// - /// The desired length of the current stream in bytes. - /// An I/O error occurs. - /// The stream does not support both writing and seeking. - /// Methods were called after the stream was closed. - public override void SetLength(long value) - { - _underlying.SetLength(value); - } - - /// - /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. - /// - /// An array of bytes. When this method returns, the buffer contains the specified byte array with the values between and ( + - 1) replaced by the bytes read from the current source. - /// The zero-based byte offset in at which to begin storing the data read from the current stream. - /// The maximum number of bytes to be read from the current stream. - /// The total number of bytes read into the buffer. - /// The sum of and is larger than the buffer length. - /// is null. - /// or is negative. - /// An I/O error occurs. - /// The stream does not support reading. - /// Methods were called after the stream was closed. - public override int Read(byte[] buffer, int offset, int count) - { - var read = _underlying.Read(buffer, offset, count); - - if (read > 0) - ReadDump.Add(buffer, offset, read); - - return read; - } - - /// - /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. - /// - /// An array of bytes. This method copies bytes from to the current stream. - /// The zero-based byte offset in at which to begin copying bytes to the current stream. - /// The number of bytes to be written to the current stream. - public override void Write(byte[] buffer, int offset, int count) - { - _underlying.Write(buffer, offset, count); - WriteDump.Add(buffer, offset, count); - } - - /// - /// Gets a value indicating whether the current stream supports reading. - /// - public override bool CanRead => _underlying.CanRead; - - /// - /// Gets a value indicating whether the current stream supports seeking. - /// - public override bool CanSeek => _underlying.CanSeek; - - /// - /// Gets a value indicating whether the current stream supports writing. - /// - public override bool CanWrite => _underlying.CanWrite; - - /// - /// Gets the length in bytes of the stream. - /// - /// A class derived from Stream does not support seeking. - /// Methods were called after the stream was closed. - public override long Length => _underlying.Length; - - /// - /// Gets or sets the position within the current stream. - /// - /// An I/O error occurs. - /// The stream does not support seeking. - /// Methods were called after the stream was closed. - public override long Position - { - get => _underlying.Position; - set => _underlying.Position = value; - } + get => _underlying.Position; + set => _underlying.Position = value; } } \ No newline at end of file diff --git a/Common/DuplicateException.cs b/Common/DuplicateException.cs index e4e08b33..379970ed 100644 --- a/Common/DuplicateException.cs +++ b/Common/DuplicateException.cs @@ -1,12 +1,11 @@ -namespace Ecng.Common -{ - using System; +namespace Ecng.Common; + +using System; - /// - /// Represents an exception that is thrown when a duplicate operation is performed. - /// - /// A message that describes the error. - public class DuplicateException(string message) : InvalidOperationException(message) - { - } +/// +/// Represents an exception that is thrown when a duplicate operation is performed. +/// +/// A message that describes the error. +public class DuplicateException(string message) : InvalidOperationException(message) +{ } \ No newline at end of file diff --git a/Common/Enumerable.cs b/Common/Enumerable.cs index 0cce74a6..85a8459b 100644 --- a/Common/Enumerable.cs +++ b/Common/Enumerable.cs @@ -1,50 +1,49 @@ -namespace Ecng.Common +namespace Ecng.Common; + +#region Using Directives + +using System.Collections; +using System.Collections.Generic; + +#endregion + +/// +/// Represents an abstract enumerable collection of elements. +/// +/// The type of elements in the collection. +public abstract class Enumerable : IEnumerable { - #region Using Directives + #region IEnumerable Members - using System.Collections; - using System.Collections.Generic; + /// + /// Returns an enumerator that iterates through the collection. + /// + /// An enumerator that can be used to iterate through the collection. + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } #endregion + #region IEnumerable Members + /// - /// Represents an abstract enumerable collection of elements. + /// Returns an enumerator that iterates through a collection. /// - /// The type of elements in the collection. - public abstract class Enumerable : IEnumerable + /// + /// An object that can be used to iterate through the collection. + /// + IEnumerator IEnumerable.GetEnumerator() { - #region IEnumerable Members - - /// - /// Returns an enumerator that iterates through the collection. - /// - /// An enumerator that can be used to iterate through the collection. - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - #endregion - - #region IEnumerable Members - - /// - /// Returns an enumerator that iterates through a collection. - /// - /// - /// An object that can be used to iterate through the collection. - /// - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - #endregion - - /// - /// When overridden in a derived class, returns an enumerator that iterates through the collection. - /// - /// An enumerator that can be used to iterate through the collection. - protected abstract IEnumerator GetEnumerator(); + return GetEnumerator(); } + + #endregion + + /// + /// When overridden in a derived class, returns an enumerator that iterates through the collection. + /// + /// An enumerator that can be used to iterate through the collection. + protected abstract IEnumerator GetEnumerator(); } \ No newline at end of file diff --git a/Common/Enumerator.cs b/Common/Enumerator.cs index 79b9b705..f621011b 100644 --- a/Common/Enumerator.cs +++ b/Common/Enumerator.cs @@ -1,295 +1,294 @@ -namespace Ecng.Common +namespace Ecng.Common; + +#region Using Directives + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; + +#endregion + +/// +/// Provides extension methods and utilities for enum types. +/// +public static class Enumerator { - #region Using Directives + /// + /// Gets the underlying base type of the enum type T. + /// + /// The enum type. + /// The underlying numeric type for the enum. + public static Type GetEnumBaseType() + { + return typeof(T).GetEnumBaseType(); + } + + /// + /// Gets the underlying type of the specified enum type. + /// + /// The enum type. + /// The underlying numeric type for the enum. + public static Type GetEnumBaseType(this Type enumType) + { + return Enum.GetUnderlyingType(enumType); + } + + /// + /// Gets the name of the specified enum value. + /// + /// The enum type. + /// The enum value. + /// The name of the enum value. + public static string GetName(T value) + { + return value.To().GetName(); + } + + /// + /// Gets the name of the enum value. + /// + /// The enum value. + /// The name of the enum value. + public static string GetName(this Enum value) + { + return Enum.GetName(value.GetType(), value); + } + + /// + /// Gets all enum values of type T. + /// + /// The enum type. + /// An enumerable of all enum values. + public static IEnumerable GetValues() + { + return typeof(T).GetValues().Cast(); + } + + /// + /// Excludes obsolete enum values from the provided collection. + /// + /// The enum type. + /// The collection of enum values. + /// An enumerable without obsolete enum values. + public static IEnumerable ExcludeObsolete(this IEnumerable values) + { + return values.Where(v => v.GetAttributeOfType() is null); + } - using System; - using System.Collections.Generic; - using System.ComponentModel; - using System.Linq; + /// + /// Gets all enum values for the specified enum type as objects. + /// + /// The enum type. + /// An enumerable of enum values as objects. + public static IEnumerable GetValues(this Type enumType) + { + return Enum.GetValues(enumType).Cast(); + } - #endregion + /// + /// Gets all names of the enum type T as a collection of strings. + /// + /// The enum type. + /// An enumerable of enum names. + public static IEnumerable GetNames() + { + return typeof(T).GetNames(); + } /// - /// Provides extension methods and utilities for enum types. + /// Gets all names of the specified enum type. /// - public static class Enumerator + /// The enum type. + /// An enumerable of enum names. + public static IEnumerable GetNames(this Type enumType) { - /// - /// Gets the underlying base type of the enum type T. - /// - /// The enum type. - /// The underlying numeric type for the enum. - public static Type GetEnumBaseType() - { - return typeof(T).GetEnumBaseType(); - } - - /// - /// Gets the underlying type of the specified enum type. - /// - /// The enum type. - /// The underlying numeric type for the enum. - public static Type GetEnumBaseType(this Type enumType) - { - return Enum.GetUnderlyingType(enumType); - } - - /// - /// Gets the name of the specified enum value. - /// - /// The enum type. - /// The enum value. - /// The name of the enum value. - public static string GetName(T value) - { - return value.To().GetName(); - } - - /// - /// Gets the name of the enum value. - /// - /// The enum value. - /// The name of the enum value. - public static string GetName(this Enum value) - { - return Enum.GetName(value.GetType(), value); - } - - /// - /// Gets all enum values of type T. - /// - /// The enum type. - /// An enumerable of all enum values. - public static IEnumerable GetValues() - { - return typeof(T).GetValues().Cast(); - } - - /// - /// Excludes obsolete enum values from the provided collection. - /// - /// The enum type. - /// The collection of enum values. - /// An enumerable without obsolete enum values. - public static IEnumerable ExcludeObsolete(this IEnumerable values) - { - return values.Where(v => v.GetAttributeOfType() is null); - } - - /// - /// Gets all enum values for the specified enum type as objects. - /// - /// The enum type. - /// An enumerable of enum values as objects. - public static IEnumerable GetValues(this Type enumType) - { - return Enum.GetValues(enumType).Cast(); - } - - /// - /// Gets all names of the enum type T as a collection of strings. - /// - /// The enum type. - /// An enumerable of enum names. - public static IEnumerable GetNames() - { - return typeof(T).GetNames(); - } - - /// - /// Gets all names of the specified enum type. - /// - /// The enum type. - /// An enumerable of enum names. - public static IEnumerable GetNames(this Type enumType) - { - return Enum.GetNames(enumType); - } - - /// - /// Determines whether the specified enum value is defined in its type. - /// - /// The enum type. - /// The enum value. - /// true if the value is defined; otherwise, false. - public static bool IsDefined(this T enumValue) - { - return Enum.IsDefined(typeof(T), enumValue); - } - - /// - /// Determines whether the specified enum type has the Flags attribute. - /// - /// The enum type. - /// true if the Flags attribute is present; otherwise, false. - public static bool IsFlags(this Type enumType) - => enumType.GetAttribute() is not null; - - /// - /// Splits the masked enum value into its constituent flag values and returns them as objects. - /// - /// The masked enum value. - /// An enumerable of individual flag values as objects. - /// Thrown when maskedValue is null. - public static IEnumerable SplitMask2(this object maskedValue) - { - if (maskedValue is null) - throw new ArgumentNullException(nameof(maskedValue)); - - return maskedValue.GetType().GetValues().Where(v => HasFlags(maskedValue, v)); - } - - /// - /// Splits the masked enum value into its constituent flag values of type T. - /// - /// The enum type. - /// The masked enum value. - /// An enumerable of individual flag values of type T. - public static IEnumerable SplitMask(this T maskedValue) - { - return GetValues().Where(v => HasFlags(maskedValue, v)); - } - - /// - /// Joins all flags of type T into a single masked enum value. - /// - /// The enum type. - /// The joined masked enum value. - public static T JoinMask() - { - return GetValues().JoinMask(); - } - - /// - /// Joins the provided collection of enum values into a single masked enum value. - /// - /// The enum type. - /// An enumerable of enum values. - /// The resulting joined masked enum value. - /// Thrown when values is null. - public static T JoinMask(this IEnumerable values) - { - if (values is null) - throw new ArgumentNullException(nameof(values)); - - return values.Aggregate(default(T), (current, t) => (current.To() | t.To()).To()); - } - - /// - /// Removes the specified flag(s) from the source enum value and returns the result. - /// - /// The enum type. - /// The source enum value. - /// The flag(s) to remove. - /// The enum value after removal of the specified flag(s). - public static T Remove(T enumSource, T enumPart) - { - return enumSource.To().Remove(enumPart); - } - - /// - /// Removes the specified flag from the enum value. - /// - /// The enum type. - /// The source enum value. - /// The flag to remove. - /// The enum value after removal of the flag. - /// Thrown when the type of enumPart does not match enumSource. - public static T Remove(this Enum enumSource, T enumPart) - { - if (enumSource.GetType() != typeof(T)) - throw new ArgumentException(nameof(enumPart)); - - return (enumSource.To() & ~enumPart.To()).To(); - } - - /// - /// Determines whether the specified enum value contains the given flag(s). (Obsolete: Use HasFlags method.) - /// - /// The enum type. - /// The source enum value. - /// The flag(s) to check for. - /// true if the source contains the flag(s); otherwise, false. - [Obsolete("Use HasFlags method.")] - public static bool Contains(T enumSource, T enumPart) - { - return HasFlags(enumSource, enumPart); - } - - /// - /// Determines whether the specified enum value has the given flag(s). - /// - /// The enum type. - /// The source enum value. - /// The flag(s) to check for. - /// true if the flag(s) are present; otherwise, false. - public static bool HasFlags(T enumSource, T enumPart) - { - return enumSource.To().HasFlag(enumPart.To()); - } - - /// - /// Determines whether the enum value contains the specified flag. (Obsolete: Use Enum.HasFlag method.) - /// - /// The source enum value. - /// The flag to check for. - /// true if the flag is present; otherwise, false. - [Obsolete("Use Enum.HasFlag method.")] - public static bool Contains(this Enum enumSource, Enum enumPart) - { - return enumSource.HasFlag(enumPart); - } - - /// - /// Attempts to parse the string to an enum value of type T. - /// - /// The enum type. - /// The string representation of the enum value. - /// When this method returns, contains the enum value equivalent to the string, if the parse succeeded. - /// if set to true ignores case during parsing. - /// true if the parse was successful; otherwise, false. - public static bool TryParse(this string str, out T value, bool ignoreCase = true) - where T : struct - { - return Enum.TryParse(str, ignoreCase, out value); - } - - // - // https://stackoverflow.com/a/9276348 - // - - /// - /// Retrieves an attribute of type TAttribute from the enum field value. - /// - /// The type of attribute to retrieve. - /// The enum value. - /// The attribute instance if found; otherwise, null. - /// Thrown when enumVal is null. - public static TAttribute GetAttributeOfType(this object enumVal) - where TAttribute : Attribute - { - if (enumVal is null) - throw new ArgumentNullException(nameof(enumVal)); - - var memInfo = enumVal.GetType().GetMember(enumVal.ToString()); - return memInfo[0].GetAttribute(false); - } - - /// - /// Determines whether the enum value is marked as browsable. - /// - /// The enum value. - /// true if the enum value is browsable; otherwise, false. - public static bool IsEnumBrowsable(this object enumVal) - => enumVal.GetAttributeOfType()?.Browsable ?? true; - - /// - /// Excludes non-browsable enum values from the collection. - /// - /// The enum type. - /// The collection of enum values. - /// An enumerable of browsable enum values. - public static IEnumerable ExcludeNonBrowsable(this IEnumerable values) - => values.Where(v => v.IsEnumBrowsable()); + return Enum.GetNames(enumType); } + + /// + /// Determines whether the specified enum value is defined in its type. + /// + /// The enum type. + /// The enum value. + /// true if the value is defined; otherwise, false. + public static bool IsDefined(this T enumValue) + { + return Enum.IsDefined(typeof(T), enumValue); + } + + /// + /// Determines whether the specified enum type has the Flags attribute. + /// + /// The enum type. + /// true if the Flags attribute is present; otherwise, false. + public static bool IsFlags(this Type enumType) + => enumType.GetAttribute() is not null; + + /// + /// Splits the masked enum value into its constituent flag values and returns them as objects. + /// + /// The masked enum value. + /// An enumerable of individual flag values as objects. + /// Thrown when maskedValue is null. + public static IEnumerable SplitMask2(this object maskedValue) + { + if (maskedValue is null) + throw new ArgumentNullException(nameof(maskedValue)); + + return maskedValue.GetType().GetValues().Where(v => HasFlags(maskedValue, v)); + } + + /// + /// Splits the masked enum value into its constituent flag values of type T. + /// + /// The enum type. + /// The masked enum value. + /// An enumerable of individual flag values of type T. + public static IEnumerable SplitMask(this T maskedValue) + { + return GetValues().Where(v => HasFlags(maskedValue, v)); + } + + /// + /// Joins all flags of type T into a single masked enum value. + /// + /// The enum type. + /// The joined masked enum value. + public static T JoinMask() + { + return GetValues().JoinMask(); + } + + /// + /// Joins the provided collection of enum values into a single masked enum value. + /// + /// The enum type. + /// An enumerable of enum values. + /// The resulting joined masked enum value. + /// Thrown when values is null. + public static T JoinMask(this IEnumerable values) + { + if (values is null) + throw new ArgumentNullException(nameof(values)); + + return values.Aggregate(default(T), (current, t) => (current.To() | t.To()).To()); + } + + /// + /// Removes the specified flag(s) from the source enum value and returns the result. + /// + /// The enum type. + /// The source enum value. + /// The flag(s) to remove. + /// The enum value after removal of the specified flag(s). + public static T Remove(T enumSource, T enumPart) + { + return enumSource.To().Remove(enumPart); + } + + /// + /// Removes the specified flag from the enum value. + /// + /// The enum type. + /// The source enum value. + /// The flag to remove. + /// The enum value after removal of the flag. + /// Thrown when the type of enumPart does not match enumSource. + public static T Remove(this Enum enumSource, T enumPart) + { + if (enumSource.GetType() != typeof(T)) + throw new ArgumentException(nameof(enumPart)); + + return (enumSource.To() & ~enumPart.To()).To(); + } + + /// + /// Determines whether the specified enum value contains the given flag(s). (Obsolete: Use HasFlags method.) + /// + /// The enum type. + /// The source enum value. + /// The flag(s) to check for. + /// true if the source contains the flag(s); otherwise, false. + [Obsolete("Use HasFlags method.")] + public static bool Contains(T enumSource, T enumPart) + { + return HasFlags(enumSource, enumPart); + } + + /// + /// Determines whether the specified enum value has the given flag(s). + /// + /// The enum type. + /// The source enum value. + /// The flag(s) to check for. + /// true if the flag(s) are present; otherwise, false. + public static bool HasFlags(T enumSource, T enumPart) + { + return enumSource.To().HasFlag(enumPart.To()); + } + + /// + /// Determines whether the enum value contains the specified flag. (Obsolete: Use Enum.HasFlag method.) + /// + /// The source enum value. + /// The flag to check for. + /// true if the flag is present; otherwise, false. + [Obsolete("Use Enum.HasFlag method.")] + public static bool Contains(this Enum enumSource, Enum enumPart) + { + return enumSource.HasFlag(enumPart); + } + + /// + /// Attempts to parse the string to an enum value of type T. + /// + /// The enum type. + /// The string representation of the enum value. + /// When this method returns, contains the enum value equivalent to the string, if the parse succeeded. + /// if set to true ignores case during parsing. + /// true if the parse was successful; otherwise, false. + public static bool TryParse(this string str, out T value, bool ignoreCase = true) + where T : struct + { + return Enum.TryParse(str, ignoreCase, out value); + } + + // + // https://stackoverflow.com/a/9276348 + // + + /// + /// Retrieves an attribute of type TAttribute from the enum field value. + /// + /// The type of attribute to retrieve. + /// The enum value. + /// The attribute instance if found; otherwise, null. + /// Thrown when enumVal is null. + public static TAttribute GetAttributeOfType(this object enumVal) + where TAttribute : Attribute + { + if (enumVal is null) + throw new ArgumentNullException(nameof(enumVal)); + + var memInfo = enumVal.GetType().GetMember(enumVal.ToString()); + return memInfo[0].GetAttribute(false); + } + + /// + /// Determines whether the enum value is marked as browsable. + /// + /// The enum value. + /// true if the enum value is browsable; otherwise, false. + public static bool IsEnumBrowsable(this object enumVal) + => enumVal.GetAttributeOfType()?.Browsable ?? true; + + /// + /// Excludes non-browsable enum values from the collection. + /// + /// The enum type. + /// The collection of enum values. + /// An enumerable of browsable enum values. + public static IEnumerable ExcludeNonBrowsable(this IEnumerable values) + => values.Where(v => v.IsEnumBrowsable()); } \ No newline at end of file diff --git a/Common/Equatable.cs b/Common/Equatable.cs index 5be33d35..9393c0f0 100644 --- a/Common/Equatable.cs +++ b/Common/Equatable.cs @@ -1,154 +1,153 @@ -namespace Ecng.Common +namespace Ecng.Common; + +using System; + +/// +/// Provides a base implementation for objects that support equality, comparison, and cloning. +/// +/// The type of the derived class. +[Serializable] +public abstract class Equatable : Cloneable, IEquatable, IComparable, IComparable + where T : class { - using System; + /// + /// Determines whether two Equatable objects are not equal. + /// + /// The left Equatable object. + /// The right object of type T. + /// true if the objects are not equal; otherwise, false. + public static bool operator !=(Equatable left, T right) + { + return !(left == right); + } + + /// + /// Determines whether two Equatable objects are not equal. + /// + /// The left Equatable object. + /// The right Equatable object. + /// true if the objects are not equal; otherwise, false. + public static bool operator !=(Equatable left, Equatable right) + { + return !(left == right); + } + + /// + /// Determines whether an Equatable object and an object of type T are equal. + /// + /// The Equatable object. + /// The object of type T. + /// true if the objects are equal; otherwise, false. + public static bool operator ==(Equatable left, T right) + { + if (ReferenceEquals(left, right)) + return true; + + return left is null ? right is null : left.Equals(right); + } + + /// + /// Determines whether two Equatable objects are equal. + /// + /// The left Equatable object. + /// The right Equatable object. + /// true if the objects are equal; otherwise, false. + public static bool operator ==(Equatable left, Equatable right) + { + if (ReferenceEquals(left, right)) + return true; + + return left is null ? right is null : left.Equals(right as T); + } + + #region IEquatable Members + + /// + /// Indicates whether the current object is equal to another object of the same type. + /// + /// An object to compare with this object. + /// true if the current object is equal to the other parameter; otherwise, false. + public virtual bool Equals(T other) + { + if (other is null) + return false; + + if (ReferenceEquals(other, this)) + return true; + + if (GetType() == other.GetType()) + return OnEquals(other); + else + return false; + } + + #endregion + + #region IComparable Members + + /// + /// Compares the current object with another object of the same type. + /// + /// An object to compare with this object. + /// + /// 0 if equal; -1 if not equal. + /// + public virtual int CompareTo(T value) + { + return Equals(value) ? 0 : -1; + } + + #endregion + + #region IComparable Members + + /// + /// Compares the current object with another object. + /// + /// An object to compare with this object. + /// + /// 0 if equal; -1 if not equal. + /// + public int CompareTo(object value) + { + return CompareTo((T)value); + } + + #endregion + + #region Object Members + + /// + /// Determines whether the specified object is equal to the current object. + /// + /// The object to compare with this object. + /// true if the specified object is equal to the current object; otherwise, false. + public override bool Equals(object obj) + { + if (obj is T t) + return Equals(t); + else + return false; + } + + /// + /// Serves as the default hash function. + /// + /// A hash code for the current object. + public override int GetHashCode() + { + return base.GetHashCode(); + } + + #endregion /// - /// Provides a base implementation for objects that support equality, comparison, and cloning. + /// Determines equality between this instance and another instance of the same type. /// - /// The type of the derived class. - [Serializable] - public abstract class Equatable : Cloneable, IEquatable, IComparable, IComparable - where T : class + /// An object to compare with this instance. + /// true if the objects are equal; otherwise, false. + protected virtual bool OnEquals(T other) { - /// - /// Determines whether two Equatable objects are not equal. - /// - /// The left Equatable object. - /// The right object of type T. - /// true if the objects are not equal; otherwise, false. - public static bool operator !=(Equatable left, T right) - { - return !(left == right); - } - - /// - /// Determines whether two Equatable objects are not equal. - /// - /// The left Equatable object. - /// The right Equatable object. - /// true if the objects are not equal; otherwise, false. - public static bool operator !=(Equatable left, Equatable right) - { - return !(left == right); - } - - /// - /// Determines whether an Equatable object and an object of type T are equal. - /// - /// The Equatable object. - /// The object of type T. - /// true if the objects are equal; otherwise, false. - public static bool operator ==(Equatable left, T right) - { - if (ReferenceEquals(left, right)) - return true; - - return left is null ? right is null : left.Equals(right); - } - - /// - /// Determines whether two Equatable objects are equal. - /// - /// The left Equatable object. - /// The right Equatable object. - /// true if the objects are equal; otherwise, false. - public static bool operator ==(Equatable left, Equatable right) - { - if (ReferenceEquals(left, right)) - return true; - - return left is null ? right is null : left.Equals(right as T); - } - - #region IEquatable Members - - /// - /// Indicates whether the current object is equal to another object of the same type. - /// - /// An object to compare with this object. - /// true if the current object is equal to the other parameter; otherwise, false. - public virtual bool Equals(T other) - { - if (other is null) - return false; - - if (ReferenceEquals(other, this)) - return true; - - if (GetType() == other.GetType()) - return OnEquals(other); - else - return false; - } - - #endregion - - #region IComparable Members - - /// - /// Compares the current object with another object of the same type. - /// - /// An object to compare with this object. - /// - /// 0 if equal; -1 if not equal. - /// - public virtual int CompareTo(T value) - { - return Equals(value) ? 0 : -1; - } - - #endregion - - #region IComparable Members - - /// - /// Compares the current object with another object. - /// - /// An object to compare with this object. - /// - /// 0 if equal; -1 if not equal. - /// - public int CompareTo(object value) - { - return CompareTo((T)value); - } - - #endregion - - #region Object Members - - /// - /// Determines whether the specified object is equal to the current object. - /// - /// The object to compare with this object. - /// true if the specified object is equal to the current object; otherwise, false. - public override bool Equals(object obj) - { - if (obj is T t) - return Equals(t); - else - return false; - } - - /// - /// Serves as the default hash function. - /// - /// A hash code for the current object. - public override int GetHashCode() - { - return base.GetHashCode(); - } - - #endregion - - /// - /// Determines equality between this instance and another instance of the same type. - /// - /// An object to compare with this instance. - /// true if the objects are equal; otherwise, false. - protected virtual bool OnEquals(T other) - { - return ReferenceEquals(this, other); - } + return ReferenceEquals(this, other); } } \ No newline at end of file diff --git a/Common/FastActivator.cs b/Common/FastActivator.cs index 2858768d..5422594f 100644 --- a/Common/FastActivator.cs +++ b/Common/FastActivator.cs @@ -1,78 +1,77 @@ -namespace Ecng.Common +namespace Ecng.Common; + +using System; +using System.Reflection; +using System.Reflection.Emit; + +/// +/// isn't supported. +/// +public class FastEmitNotSupported { - using System; - using System.Reflection; - using System.Reflection.Emit; +} +/// +/// Fast alternative to Activator.CreateInstance<T> for reference types with default constructor +/// +/// +/// Измененная версия FastObjectFactory2<T> отсюда: +/// http://stackoverflow.com/questions/2024435/how-to-pass-ctor-args-in-activator-createinstance-or-use-il +/// +public static class FastActivator + // чтобы можно было создавать классы с закрытым конструктором + //where T :new() +{ /// - /// isn't supported. + /// Not supported mode. /// - public class FastEmitNotSupported - { - } + public static bool NotSupported { get; set; } /// - /// Fast alternative to Activator.CreateInstance<T> for reference types with default constructor + /// Create object handler. /// - /// - /// Измененная версия FastObjectFactory2<T> отсюда: - /// http://stackoverflow.com/questions/2024435/how-to-pass-ctor-args-in-activator-createinstance-or-use-il - /// - public static class FastActivator - // чтобы можно было создавать классы с закрытым конструктором - //where T :new() - { - /// - /// Not supported mode. - /// - public static bool NotSupported { get; set; } + public static Func CreateObject { get; } - /// - /// Create object handler. - /// - public static Func CreateObject { get; } + static FastActivator() + { + var objType = typeof(T); + var cinfo = objType.GetConstructor(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic, null, Type.EmptyTypes, null); - static FastActivator() + if ((NotSupported || Scope.Current != null) || objType.IsValueType || cinfo is null) { - var objType = typeof(T); - var cinfo = objType.GetConstructor(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic, null, Type.EmptyTypes, null); + CreateObject = Activator.CreateInstance; + } + else + { + DynamicMethod dynMethod; - if ((NotSupported || Scope.Current != null) || objType.IsValueType || cinfo is null) + try { - CreateObject = Activator.CreateInstance; + dynMethod = new DynamicMethod("DM$OBJ_FACTORY_" + objType.Name, objType, null, typeof(T), true); } - else + catch (PlatformNotSupportedException) { - DynamicMethod dynMethod; - - try - { - dynMethod = new DynamicMethod("DM$OBJ_FACTORY_" + objType.Name, objType, null, typeof(T), true); - } - catch (PlatformNotSupportedException) - { - NotSupported = true; - CreateObject = Activator.CreateInstance; - return; - } + NotSupported = true; + CreateObject = Activator.CreateInstance; + return; + } - ILGenerator ilGen = dynMethod.GetILGenerator(); - ilGen.Emit(OpCodes.Newobj, cinfo); - ilGen.Emit(OpCodes.Ret); - CreateObject = (Func)dynMethod.CreateDelegate(typeof(Func)); + ILGenerator ilGen = dynMethod.GetILGenerator(); + ilGen.Emit(OpCodes.Newobj, cinfo); + ilGen.Emit(OpCodes.Ret); + CreateObject = (Func)dynMethod.CreateDelegate(typeof(Func)); - // mika - // если будет Disposable объект, то проверочный запуск приведет к подвисшему в памяти объекту с неуправляемыми ресурсами - // - //try - //{ - // CreateObject(); - //} - //catch - //{ - // CreateObject = () => new T(); - //} - } + // mika + // если будет Disposable объект, то проверочный запуск приведет к подвисшему в памяти объекту с неуправляемыми ресурсами + // + //try + //{ + // CreateObject(); + //} + //catch + //{ + // CreateObject = () => new T(); + //} } } } diff --git a/Common/FastCsvReader.cs b/Common/FastCsvReader.cs index 946b571d..304046a4 100644 --- a/Common/FastCsvReader.cs +++ b/Common/FastCsvReader.cs @@ -1,607 +1,606 @@ -namespace Ecng.Common +namespace Ecng.Common; + +using System; +using System.IO; +using System.Numerics; +using System.Text; + +/// +/// Provides fast CSV reading capabilities from various input sources. +/// +public class FastCsvReader { - using System; - using System.IO; - using System.Numerics; - using System.Text; + private static readonly Func _toBool = Converter.GetTypedConverter(); + private static readonly Func _toDouble = Converter.GetTypedConverter(); + + private const int _buffSize = FileSizes.MB; + private readonly char[] _buffer = new char[_buffSize]; + private int _bufferLen; + private int _bufferPos; + private char[] _line = new char[_buffSize]; + private int _lineLen; + private RefPair[] _columnPos = new RefPair[_buffSize]; + private readonly char[] _lineSeparatorChars; + + private int _lineSeparatorCharPos; /// - /// Provides fast CSV reading capabilities from various input sources. + /// Initializes a new instance of the class using a stream, encoding, and line separator. /// - public class FastCsvReader + /// The input stream to read CSV data from. + /// The character encoding to use. + /// The string that separates lines. + public FastCsvReader(Stream stream, Encoding encoding, string lineSeparator) + : this(new StreamReader(stream, encoding), lineSeparator) { - private static readonly Func _toBool = Converter.GetTypedConverter(); - private static readonly Func _toDouble = Converter.GetTypedConverter(); - - private const int _buffSize = FileSizes.MB; - private readonly char[] _buffer = new char[_buffSize]; - private int _bufferLen; - private int _bufferPos; - private char[] _line = new char[_buffSize]; - private int _lineLen; - private RefPair[] _columnPos = new RefPair[_buffSize]; - private readonly char[] _lineSeparatorChars; - - private int _lineSeparatorCharPos; - - /// - /// Initializes a new instance of the class using a stream, encoding, and line separator. - /// - /// The input stream to read CSV data from. - /// The character encoding to use. - /// The string that separates lines. - public FastCsvReader(Stream stream, Encoding encoding, string lineSeparator) - : this(new StreamReader(stream, encoding), lineSeparator) - { - } + } - /// - /// Initializes a new instance of the class using a string content and line separator. - /// - /// The string content to read CSV data from. - /// The string that separates lines. - public FastCsvReader(string content, string lineSeparator) - : this(new StringReader(content), lineSeparator) - { - } + /// + /// Initializes a new instance of the class using a string content and line separator. + /// + /// The string content to read CSV data from. + /// The string that separates lines. + public FastCsvReader(string content, string lineSeparator) + : this(new StringReader(content), lineSeparator) + { + } - /// - /// Initializes a new instance of the class using a TextReader and line separator. - /// - /// The TextReader to read CSV data from. - /// The string that separates lines. - public FastCsvReader(TextReader reader, string lineSeparator) - { - if (lineSeparator.IsEmpty()) - throw new ArgumentNullException(nameof(lineSeparator)); + /// + /// Initializes a new instance of the class using a TextReader and line separator. + /// + /// The TextReader to read CSV data from. + /// The string that separates lines. + public FastCsvReader(TextReader reader, string lineSeparator) + { + if (lineSeparator.IsEmpty()) + throw new ArgumentNullException(nameof(lineSeparator)); - Reader = reader ?? throw new ArgumentNullException(nameof(reader)); - _lineSeparatorChars = [.. lineSeparator]; + Reader = reader ?? throw new ArgumentNullException(nameof(reader)); + _lineSeparatorChars = [.. lineSeparator]; - for (var i = 0; i < _columnPos.Length; i++) - _columnPos[i] = new RefPair(); - } + for (var i = 0; i < _columnPos.Length; i++) + _columnPos[i] = new RefPair(); + } - /// - /// Gets the underlying text reader. - /// - public TextReader Reader { get; } + /// + /// Gets the underlying text reader. + /// + public TextReader Reader { get; } - /// - /// Gets or sets the character used to separate columns. - /// - public char ColumnSeparator { get; set; } = ';'; + /// + /// Gets or sets the character used to separate columns. + /// + public char ColumnSeparator { get; set; } = ';'; - /// - /// Gets the current CSV line as a string. - /// - public string CurrentLine + /// + /// Gets the current CSV line as a string. + /// + public string CurrentLine + { + get { - get - { - if (_lineLen == 0) - return null; + if (_lineLen == 0) + return null; - return new string(_line, 0, _lineLen); - } + return new string(_line, 0, _lineLen); } + } - private int _columnCount; + private int _columnCount; - /// - /// Gets the number of columns in the current CSV line. - /// - public int ColumnCount => _columnCount; + /// + /// Gets the number of columns in the current CSV line. + /// + public int ColumnCount => _columnCount; + + private int _columnCurr; - private int _columnCurr; + /// + /// Gets the current column index being processed. + /// + public int ColumnCurr => _columnCurr; - /// - /// Gets the current column index being processed. - /// - public int ColumnCurr => _columnCurr; + private RefPair GetColumnPos() + { + var prevLen = _columnPos.Length; - private RefPair GetColumnPos() + if (prevLen <= _columnCount) { - var prevLen = _columnPos.Length; + Array.Resize(ref _columnPos, prevLen + _buffSize); - if (prevLen <= _columnCount) + for (var i = prevLen; i < prevLen + _buffSize; i++) { - Array.Resize(ref _columnPos, prevLen + _buffSize); - - for (var i = prevLen; i < prevLen + _buffSize; i++) - { - _columnPos[i] = new RefPair(); - } + _columnPos[i] = new RefPair(); } - - return _columnPos[_columnCount]; } - /// - /// Reads the next CSV line from the underlying stream or reader. - /// - /// true if a new line was successfully read; otherwise, false. - public bool NextLine() - { - _lineLen = 0; - _columnCount = 0; - _columnCurr = -1; - - var inQuote = false; - var columnStart = 0; + return _columnPos[_columnCount]; + } - while (true) - { - if (_bufferPos >= _bufferLen) - { - _bufferPos = 0; - _bufferLen = 0; + /// + /// Reads the next CSV line from the underlying stream or reader. + /// + /// true if a new line was successfully read; otherwise, false. + public bool NextLine() + { + _lineLen = 0; + _columnCount = 0; + _columnCurr = -1; - var left = _buffer.Length; + var inQuote = false; + var columnStart = 0; - while (left > 0) - { - var read = Reader.ReadBlock(_buffer, 0, _buffer.Length); + while (true) + { + if (_bufferPos >= _bufferLen) + { + _bufferPos = 0; + _bufferLen = 0; - if (read == 0) - break; + var left = _buffer.Length; - left -= read; - _bufferLen += read; - } + while (left > 0) + { + var read = Reader.ReadBlock(_buffer, 0, _buffer.Length); - if (_bufferLen == 0) + if (read == 0) break; + + left -= read; + _bufferLen += read; } - var c = _buffer[_bufferPos++]; + if (_bufferLen == 0) + break; + } - if (!inQuote) - { - if (c == _lineSeparatorChars[_lineSeparatorCharPos]) - { - _lineSeparatorCharPos++; + var c = _buffer[_bufferPos++]; - if (_lineSeparatorCharPos == _lineSeparatorChars.Length) - { - _lineSeparatorCharPos = 0; - break; - } + if (!inQuote) + { + if (c == _lineSeparatorChars[_lineSeparatorCharPos]) + { + _lineSeparatorCharPos++; - continue; - } - else if (_lineSeparatorCharPos > 0) + if (_lineSeparatorCharPos == _lineSeparatorChars.Length) { - if ((_lineLen + _lineSeparatorCharPos) >= _line.Length) - Array.Resize(ref _line, _line.Length + _buffSize); - - Array.Copy(_lineSeparatorChars, 0, _line, _lineLen, _lineSeparatorCharPos); - - _lineLen += _lineSeparatorCharPos; _lineSeparatorCharPos = 0; + break; } - if (c == ColumnSeparator) - { - if (columnStart > _lineLen) - throw new InvalidOperationException(); - - var pair = GetColumnPos(); - - pair.First = columnStart; - pair.Second = _lineLen; + continue; + } + else if (_lineSeparatorCharPos > 0) + { + if ((_lineLen + _lineSeparatorCharPos) >= _line.Length) + Array.Resize(ref _line, _line.Length + _buffSize); - columnStart = _lineLen + 1; + Array.Copy(_lineSeparatorChars, 0, _line, _lineLen, _lineSeparatorCharPos); - _columnCount++; - } + _lineLen += _lineSeparatorCharPos; + _lineSeparatorCharPos = 0; } - if (c == '"') + if (c == ColumnSeparator) { - inQuote = !inQuote; - - if (inQuote && _bufferPos > 1 && _buffer[_bufferPos - 2] == '"') - { - if (_lineLen >= _line.Length) - Array.Resize(ref _line, _line.Length + _buffSize); + if (columnStart > _lineLen) + throw new InvalidOperationException(); - _line[_lineLen++] = c; - } + var pair = GetColumnPos(); - //if (inQuote) - continue; - //else - // break; - } + pair.First = columnStart; + pair.Second = _lineLen; - if (_lineLen >= _line.Length) - Array.Resize(ref _line, _line.Length + _buffSize); + columnStart = _lineLen + 1; - _line[_lineLen++] = c; + _columnCount++; + } } - if (_columnCount > 0) + if (c == '"') { - var pair = GetColumnPos(); + inQuote = !inQuote; - pair.First = columnStart; - pair.Second = _lineLen; + if (inQuote && _bufferPos > 1 && _buffer[_bufferPos - 2] == '"') + { + if (_lineLen >= _line.Length) + Array.Resize(ref _line, _line.Length + _buffSize); + + _line[_lineLen++] = c; + } - _columnCount++; + //if (inQuote) + continue; + //else + // break; } - return _lineLen > 0; + if (_lineLen >= _line.Length) + Array.Resize(ref _line, _line.Length + _buffSize); + + _line[_lineLen++] = c; } - /// - /// Skips the specified number of columns. - /// - /// The number of columns to skip. Defaults to 1. - /// Thrown if the count is less than or equal to 0. - /// Thrown if skipping the specified columns would exceed the number of columns available in the current line. - public void Skip(int count = 1) + if (_columnCount > 0) { - if (count <= 0) - throw new ArgumentOutOfRangeException(nameof(count)); + var pair = GetColumnPos(); - if ((_columnCurr + count) >= _columnCount) - throw new ArgumentException(nameof(count)); + pair.First = columnStart; + pair.Second = _lineLen; - _columnCurr += count; + _columnCount++; } - /// - /// Reads the next column as a boolean value. - /// - /// The boolean value read from the column. - public bool ReadBool() - { - return ReadNullableBool().Value; - } + return _lineLen > 0; + } - /// - /// Reads the next column as a nullable boolean value. - /// - /// A nullable boolean value read from the column, or null if the column is empty. - public bool? ReadNullableBool() - { - var str = ReadString(); + /// + /// Skips the specified number of columns. + /// + /// The number of columns to skip. Defaults to 1. + /// Thrown if the count is less than or equal to 0. + /// Thrown if skipping the specified columns would exceed the number of columns available in the current line. + public void Skip(int count = 1) + { + if (count <= 0) + throw new ArgumentOutOfRangeException(nameof(count)); - if (str is null) - return null; + if ((_columnCurr + count) >= _columnCount) + throw new ArgumentException(nameof(count)); - return _toBool(str); - } + _columnCurr += count; + } - /// - /// Reads the next column as an enum value of type . - /// - /// The enum type to convert to. - /// The enum value read from the column. - public T ReadEnum() - where T : struct - { - return ReadNullableEnum().Value; - } + /// + /// Reads the next column as a boolean value. + /// + /// The boolean value read from the column. + public bool ReadBool() + { + return ReadNullableBool().Value; + } - /// - /// Reads the next column as a nullable enum value of type . - /// - /// The enum type to convert to. - /// A nullable enum value read from the column, or null if the column is empty. - public T? ReadNullableEnum() - where T : struct - { - var str = ReadString(); + /// + /// Reads the next column as a nullable boolean value. + /// + /// A nullable boolean value read from the column, or null if the column is empty. + public bool? ReadNullableBool() + { + var str = ReadString(); - if (str.IsEmpty()) - return null; + if (str is null) + return null; - if (int.TryParse(str, out var num)) - return num.To(); + return _toBool(str); + } - return str.To(); - } + /// + /// Reads the next column as an enum value of type . + /// + /// The enum type to convert to. + /// The enum value read from the column. + public T ReadEnum() + where T : struct + { + return ReadNullableEnum().Value; + } - /// - /// Reads the next column as a double value. - /// - /// The double value read from the column. - public double ReadDouble() - { - return ReadNullableDouble().Value; - } + /// + /// Reads the next column as a nullable enum value of type . + /// + /// The enum type to convert to. + /// A nullable enum value read from the column, or null if the column is empty. + public T? ReadNullableEnum() + where T : struct + { + var str = ReadString(); - /// - /// Reads the next column as a nullable double value. - /// - /// A nullable double value read from the column, or null if the column is empty. - public double? ReadNullableDouble() - { - var str = ReadString(); + if (str.IsEmpty()) + return null; - if (str is null) - return null; + if (int.TryParse(str, out var num)) + return num.To(); - return _toDouble(str); - } + return str.To(); + } - /// - /// Reads the next column as a decimal value. - /// - /// The decimal value read from the column. - public decimal ReadDecimal() - { - return ReadNullableDecimal().Value; - } + /// + /// Reads the next column as a double value. + /// + /// The double value read from the column. + public double ReadDouble() + { + return ReadNullableDouble().Value; + } - /// - /// Reads the next column as a nullable decimal value. - /// - /// A nullable decimal value read from the column, or null if the column is empty. - public decimal? ReadNullableDecimal() - { - var pair = GetNextColumnPos(); + /// + /// Reads the next column as a nullable double value. + /// + /// A nullable double value read from the column, or null if the column is empty. + public double? ReadNullableDouble() + { + var str = ReadString(); - if (pair.First == pair.Second) - return null; + if (str is null) + return null; - long? intPart = null; - BigInteger? intPart2 = null; - long? fractalPart = null; + return _toDouble(str); + } - var isNegative = false; + /// + /// Reads the next column as a decimal value. + /// + /// The decimal value read from the column. + public decimal ReadDecimal() + { + return ReadNullableDecimal().Value; + } - int i; + /// + /// Reads the next column as a nullable decimal value. + /// + /// A nullable decimal value read from the column, or null if the column is empty. + public decimal? ReadNullableDecimal() + { + var pair = GetNextColumnPos(); - for (i = pair.First; i < pair.Second; i++) - { - var c = _line[i]; + if (pair.First == pair.Second) + return null; - if (c == '.') - break; + long? intPart = null; + BigInteger? intPart2 = null; + long? fractalPart = null; - if (c == '+') - { - if (i != pair.First) - throw new InvalidOperationException("+"); + var isNegative = false; - continue; - } + int i; - if (c == '-') - { - if (i != pair.First) - throw new InvalidOperationException("-"); + for (i = pair.First; i < pair.Second; i++) + { + var c = _line[i]; - isNegative = true; - continue; - } + if (c == '.') + break; - if (intPart2 is null) - { - intPart = (intPart ?? 0) * 10 + (c - '0'); + if (c == '+') + { + if (i != pair.First) + throw new InvalidOperationException("+"); - if (intPart >= long.MaxValue / 10) - { - intPart2 = intPart; - intPart = null; - } - } - else - intPart2 = intPart2.Value * 10 + (c - '0'); + continue; } - var canSkipZero = true; - var fractalPartSize = 0; - - for (var j = pair.Second - 1; j > i; j--) + if (c == '-') { - var c = _line[j]; + if (i != pair.First) + throw new InvalidOperationException("-"); - if (c == '.') - throw new InvalidOperationException(); - - if (c == '0' && canSkipZero) - continue; + isNegative = true; + continue; + } - canSkipZero = false; - fractalPartSize++; + if (intPart2 is null) + { + intPart = (intPart ?? 0) * 10 + (c - '0'); - fractalPart = (c - '0') * 10.Pow(fractalPartSize - 1) + (fractalPart ?? 0); + if (intPart >= long.MaxValue / 10) + { + intPart2 = intPart; + intPart = null; + } } + else + intPart2 = intPart2.Value * 10 + (c - '0'); + } - decimal? retVal = intPart ?? (decimal?)intPart2; + var canSkipZero = true; + var fractalPartSize = 0; - if (fractalPart is object) - { - if (intPart2 is object) - retVal = (decimal)intPart2.Value + (decimal)fractalPart.Value / (long)Math.Pow(10, fractalPartSize); - else - retVal = (intPart ?? 0L) + (decimal)fractalPart.Value / (long)Math.Pow(10, fractalPartSize); - } + for (var j = pair.Second - 1; j > i; j--) + { + var c = _line[j]; + + if (c == '.') + throw new InvalidOperationException(); - if (retVal > 0 && isNegative) - retVal = -retVal; + if (c == '0' && canSkipZero) + continue; - return retVal; + canSkipZero = false; + fractalPartSize++; + + fractalPart = (c - '0') * 10.Pow(fractalPartSize - 1) + (fractalPart ?? 0); } - /// - /// Reads the next column as an integer value. - /// - /// The integer value read from the column. - public int ReadInt() + decimal? retVal = intPart ?? (decimal?)intPart2; + + if (fractalPart is object) { - return ReadNullableInt().Value; + if (intPart2 is object) + retVal = (decimal)intPart2.Value + (decimal)fractalPart.Value / (long)Math.Pow(10, fractalPartSize); + else + retVal = (intPart ?? 0L) + (decimal)fractalPart.Value / (long)Math.Pow(10, fractalPartSize); } - /// - /// Reads the next column as a nullable integer value. - /// - /// A nullable integer value read from the column, or null if the column is empty. - public int? ReadNullableInt() - { - var pair = GetNextColumnPos(); + if (retVal > 0 && isNegative) + retVal = -retVal; - if (pair.First == pair.Second) - return null; + return retVal; + } - var isNegative = false; + /// + /// Reads the next column as an integer value. + /// + /// The integer value read from the column. + public int ReadInt() + { + return ReadNullableInt().Value; + } - var retVal = 0; + /// + /// Reads the next column as a nullable integer value. + /// + /// A nullable integer value read from the column, or null if the column is empty. + public int? ReadNullableInt() + { + var pair = GetNextColumnPos(); - for (var i = pair.First; i < pair.Second; i++) - { - var c = _line[i]; + if (pair.First == pair.Second) + return null; - if (c == '+') - { - if (i != pair.First) - throw new InvalidOperationException("+"); + var isNegative = false; - continue; - } + var retVal = 0; - if (c == '-') - { - if (i != pair.First) - throw new InvalidOperationException("-"); + for (var i = pair.First; i < pair.Second; i++) + { + var c = _line[i]; - isNegative = true; - continue; - } + if (c == '+') + { + if (i != pair.First) + throw new InvalidOperationException("+"); - retVal = retVal * 10 + (c - '0'); + continue; } - if (retVal > 0 && isNegative) - retVal = -retVal; + if (c == '-') + { + if (i != pair.First) + throw new InvalidOperationException("-"); - return retVal; - } + isNegative = true; + continue; + } - /// - /// Reads the next column as a long value. - /// - /// The long value read from the column. - public long ReadLong() - { - return ReadNullableLong().Value; + retVal = retVal * 10 + (c - '0'); } - /// - /// Reads the next column as a nullable long value. - /// - /// A nullable long value read from the column, or null if the column is empty. - public long? ReadNullableLong() - { - var pair = GetNextColumnPos(); + if (retVal > 0 && isNegative) + retVal = -retVal; - if (pair.First == pair.Second) - return null; + return retVal; + } - var isNegative = false; + /// + /// Reads the next column as a long value. + /// + /// The long value read from the column. + public long ReadLong() + { + return ReadNullableLong().Value; + } - var retVal = 0L; + /// + /// Reads the next column as a nullable long value. + /// + /// A nullable long value read from the column, or null if the column is empty. + public long? ReadNullableLong() + { + var pair = GetNextColumnPos(); - for (var i = pair.First; i < pair.Second; i++) - { - var c = _line[i]; + if (pair.First == pair.Second) + return null; - if (c == '+') - { - if (i != pair.First) - throw new InvalidOperationException("+"); + var isNegative = false; - continue; - } + var retVal = 0L; - if (c == '-') - { - if (i != pair.First) - throw new InvalidOperationException("-"); + for (var i = pair.First; i < pair.Second; i++) + { + var c = _line[i]; - isNegative = true; - continue; - } + if (c == '+') + { + if (i != pair.First) + throw new InvalidOperationException("+"); - retVal = retVal * 10 + (c - '0'); + continue; } - if (retVal > 0 && isNegative) - retVal = -retVal; + if (c == '-') + { + if (i != pair.First) + throw new InvalidOperationException("-"); + + isNegative = true; + continue; + } - return retVal; + retVal = retVal * 10 + (c - '0'); } - /// - /// Reads the next column as a string. - /// - /// The string read from the column, or null if the column is empty. - public string ReadString() - { - var pair = GetNextColumnPos(); + if (retVal > 0 && isNegative) + retVal = -retVal; - var len = pair.Second - pair.First; + return retVal; + } - if (len == 0) - return null; + /// + /// Reads the next column as a string. + /// + /// The string read from the column, or null if the column is empty. + public string ReadString() + { + var pair = GetNextColumnPos(); - return new string(_line, pair.First, len); - } + var len = pair.Second - pair.First; - /// - /// Reads the next column as a DateTime value using the specified format. - /// - /// The date and time format string. - /// The DateTime value read from the column. - public DateTime ReadDateTime(string format) - { - return ReadNullableDateTime(format).Value; - } + if (len == 0) + return null; - /// - /// Reads the next column as a nullable DateTime value using the specified format. - /// - /// The date and time format string. - /// A nullable DateTime value read from the column, or null if the column is empty. - public DateTime? ReadNullableDateTime(string format) - { - var str = ReadString(); + return new string(_line, pair.First, len); + } - return str?.ToDateTime(format); - } + /// + /// Reads the next column as a DateTime value using the specified format. + /// + /// The date and time format string. + /// The DateTime value read from the column. + public DateTime ReadDateTime(string format) + { + return ReadNullableDateTime(format).Value; + } - /// - /// Reads the next column as a TimeSpan value using the specified format. - /// - /// The time span format string. - /// The TimeSpan value read from the column. - public TimeSpan ReadTimeSpan(string format) - { - return ReadNullableTimeSpan(format).Value; - } + /// + /// Reads the next column as a nullable DateTime value using the specified format. + /// + /// The date and time format string. + /// A nullable DateTime value read from the column, or null if the column is empty. + public DateTime? ReadNullableDateTime(string format) + { + var str = ReadString(); - /// - /// Reads the next column as a nullable TimeSpan value using the specified format. - /// - /// The time span format string. - /// A nullable TimeSpan value read from the column, or null if the column is empty. - public TimeSpan? ReadNullableTimeSpan(string format) - { - var str = ReadString(); + return str?.ToDateTime(format); + } - return str?.ToTimeSpan(format); - } + /// + /// Reads the next column as a TimeSpan value using the specified format. + /// + /// The time span format string. + /// The TimeSpan value read from the column. + public TimeSpan ReadTimeSpan(string format) + { + return ReadNullableTimeSpan(format).Value; + } - private RefPair GetNextColumnPos() - { - if (_columnCurr >= _columnCount) - throw new InvalidOperationException(); + /// + /// Reads the next column as a nullable TimeSpan value using the specified format. + /// + /// The time span format string. + /// A nullable TimeSpan value read from the column, or null if the column is empty. + public TimeSpan? ReadNullableTimeSpan(string format) + { + var str = ReadString(); - return _columnPos[++_columnCurr]; - } + return str?.ToTimeSpan(format); + } + + private RefPair GetNextColumnPos() + { + if (_columnCurr >= _columnCount) + throw new InvalidOperationException(); + + return _columnPos[++_columnCurr]; } } \ No newline at end of file diff --git a/Common/FastDateTimeParser.cs b/Common/FastDateTimeParser.cs index a4bdb3cc..a03b57cf 100644 --- a/Common/FastDateTimeParser.cs +++ b/Common/FastDateTimeParser.cs @@ -1,331 +1,330 @@ -namespace Ecng.Common -{ - using System; - using System.Collections.Generic; - using System.Text; +namespace Ecng.Common; - /// - /// Provides methods to parse and format date and time strings using a fast custom parser. - /// - public class FastDateTimeParser +using System; +using System.Collections.Generic; +using System.Text; + +/// +/// Provides methods to parse and format date and time strings using a fast custom parser. +/// +public class FastDateTimeParser +{ + private enum Parts { - private enum Parts - { - String, - Year, - Month, - Day, - Hour, - Minute, - Second, - Mls, - Mcs, - Nano, - } + String, + Year, + Month, + Day, + Hour, + Minute, + Second, + Mls, + Mcs, + Nano, + } - private readonly Tuple[] _parts; - - private readonly int _yearStart; - private readonly int _monthStart; - private readonly int _dayStart; - private readonly int _hourStart; - private readonly int _minuteStart; - private readonly int _secondStart; - private readonly int _milliStart; - private readonly int _microStart; - private readonly int _nanoStart; - - private readonly int _timeZoneStart; - - private readonly bool _isYearTwoChars; - private readonly bool _isMonthTwoChars = true; - private readonly bool _isDayTwoChars = true; - - /// - /// Gets the date and time format template used for parsing and formatting. - /// - public string Template { get; } - - /// - /// Initializes a new instance of the class with the specified format template. - /// - /// The date and time format template. - /// Thrown when the template is null or empty. - public FastDateTimeParser(string template) - { - if (template.IsEmpty()) - throw new ArgumentNullException(nameof(template)); + private readonly Tuple[] _parts; - Template = template; + private readonly int _yearStart; + private readonly int _monthStart; + private readonly int _dayStart; + private readonly int _hourStart; + private readonly int _minuteStart; + private readonly int _secondStart; + private readonly int _milliStart; + private readonly int _microStart; + private readonly int _nanoStart; - _yearStart = template.IndexOf('y'); - _monthStart = template.IndexOf('M'); - _dayStart = template.IndexOf('d'); - _hourStart = template.IndexOf('H'); - _minuteStart = template.IndexOf('m'); - _secondStart = template.IndexOf('s'); - _milliStart = template.IndexOf('f'); - _timeZoneStart = template.IndexOf('z'); + private readonly int _timeZoneStart; - if (_yearStart == -1) - _yearStart = template.IndexOf('Y'); + private readonly bool _isYearTwoChars; + private readonly bool _isMonthTwoChars = true; + private readonly bool _isDayTwoChars = true; - if (_dayStart == -1) - _dayStart = template.IndexOf('D'); + /// + /// Gets the date and time format template used for parsing and formatting. + /// + public string Template { get; } - if (_milliStart == -1) - _milliStart = template.IndexOf('F'); + /// + /// Initializes a new instance of the class with the specified format template. + /// + /// The date and time format template. + /// Thrown when the template is null or empty. + public FastDateTimeParser(string template) + { + if (template.IsEmpty()) + throw new ArgumentNullException(nameof(template)); - if (_monthStart == -1 && _minuteStart != -1 && _hourStart == -1) - { - _monthStart = _minuteStart; - _minuteStart = -1; - } + Template = template; - if (_yearStart != -1) - _isYearTwoChars = template.Length < _yearStart + 3 || template[_yearStart + 2] != template[_yearStart]; + _yearStart = template.IndexOf('y'); + _monthStart = template.IndexOf('M'); + _dayStart = template.IndexOf('d'); + _hourStart = template.IndexOf('H'); + _minuteStart = template.IndexOf('m'); + _secondStart = template.IndexOf('s'); + _milliStart = template.IndexOf('f'); + _timeZoneStart = template.IndexOf('z'); - if (_monthStart != -1) - _isMonthTwoChars = template.Length > _monthStart + 1 && template[_monthStart + 1] == template[_monthStart]; + if (_yearStart == -1) + _yearStart = template.IndexOf('Y'); - if (_dayStart != -1) - _isDayTwoChars = template.Length > _dayStart + 1 && template[_dayStart + 1] == template[_dayStart]; + if (_dayStart == -1) + _dayStart = template.IndexOf('D'); - var parts = new SortedList(); + if (_milliStart == -1) + _milliStart = template.IndexOf('F'); - if (_yearStart != -1) - parts.Add(_yearStart, Parts.Year); + if (_monthStart == -1 && _minuteStart != -1 && _hourStart == -1) + { + _monthStart = _minuteStart; + _minuteStart = -1; + } - if (_monthStart != -1) - parts.Add(_monthStart, Parts.Month); + if (_yearStart != -1) + _isYearTwoChars = template.Length < _yearStart + 3 || template[_yearStart + 2] != template[_yearStart]; - if (_dayStart != -1) - parts.Add(_dayStart, Parts.Day); + if (_monthStart != -1) + _isMonthTwoChars = template.Length > _monthStart + 1 && template[_monthStart + 1] == template[_monthStart]; - if (_hourStart != -1) - parts.Add(_hourStart, Parts.Hour); + if (_dayStart != -1) + _isDayTwoChars = template.Length > _dayStart + 1 && template[_dayStart + 1] == template[_dayStart]; - if (_minuteStart != -1) - parts.Add(_minuteStart, Parts.Minute); + var parts = new SortedList(); - if (_secondStart != -1) - parts.Add(_secondStart, Parts.Second); + if (_yearStart != -1) + parts.Add(_yearStart, Parts.Year); - _microStart = -1; - _nanoStart = -1; + if (_monthStart != -1) + parts.Add(_monthStart, Parts.Month); - if (_milliStart != -1) - { - parts.Add(_milliStart, Parts.Mls); + if (_dayStart != -1) + parts.Add(_dayStart, Parts.Day); - var microStart = _milliStart + 3; + if (_hourStart != -1) + parts.Add(_hourStart, Parts.Hour); - if (template.Length > (microStart + 1) && (template[microStart] == 'f' || template[microStart] == 'F')) - { - _microStart = microStart; - parts.Add(_microStart, Parts.Mcs); + if (_minuteStart != -1) + parts.Add(_minuteStart, Parts.Minute); - var nanoStart = _microStart + 3; + if (_secondStart != -1) + parts.Add(_secondStart, Parts.Second); - if (template.Length > (nanoStart + 1) && (template[nanoStart] == 'f' || template[nanoStart] == 'F')) - { - _nanoStart = nanoStart; - parts.Add(_nanoStart, Parts.Nano); - } - } - } + _microStart = -1; + _nanoStart = -1; - var parts2 = new List>(); + if (_milliStart != -1) + { + parts.Add(_milliStart, Parts.Mls); - var prevIndex = 0; + var microStart = _milliStart + 3; - foreach (var part in parts) + if (template.Length > (microStart + 1) && (template[microStart] == 'f' || template[microStart] == 'F')) { - if (prevIndex != part.Key) - { - var len = part.Key - prevIndex; - parts2.Add(Tuple.Create(Parts.String, template.Substring(prevIndex, len))); - prevIndex += len; - } + _microStart = microStart; + parts.Add(_microStart, Parts.Mcs); - parts2.Add(Tuple.Create(part.Value, (string)null)); + var nanoStart = _microStart + 3; - prevIndex += part.Value switch + if (template.Length > (nanoStart + 1) && (template[nanoStart] == 'f' || template[nanoStart] == 'F')) { - Parts.Year => _isYearTwoChars ? 2 : 4, - Parts.Month => _isMonthTwoChars ? 2 : 1, - Parts.Day => _isDayTwoChars ? 2 : 1, - Parts.Mls => 3, - Parts.Mcs => 3, - Parts.Nano => 3, - _ => 2, - }; + _nanoStart = nanoStart; + parts.Add(_nanoStart, Parts.Nano); + } } + } - _parts = [.. parts2]; + var parts2 = new List>(); - //TimeHelper.InitBounds(template, 'y', out _yearStart, out _yearLen); - //TimeHelper.InitBounds(template, 'M', out _monthStart, out _monthLen); - //TimeHelper.InitBounds(template, 'd', out _dayStart, out _dayLen); - //TimeHelper.InitBounds(template, 'H', out _hourStart, out _hourLen); - //TimeHelper.InitBounds(template, 'm', out _minuteStart, out _minuteLen); - //TimeHelper.InitBounds(template, 's', out _secondStart, out _secondLen); - //TimeHelper.InitBounds(template, 'f', out _milliStart, out _milliLen); - } + var prevIndex = 0; - /// - /// Parses the specified input string into a based on the predefined template. - /// - /// The string input representing the date and time. - /// A object parsed from the input string. - /// Thrown when the input cannot be parsed into a valid . - public DateTime Parse(string input) + foreach (var part in parts) { - try + if (prevIndex != part.Key) { - //fixed (char* stringBuffer = input) - //{ - var years = _yearStart == -1 ? DateTime.Now.Year : (_isYearTwoChars ? ((DateTime.Now.Year / 1000) * 1000 + (input[_yearStart] - '0') * 10 + (input[_yearStart + 1] - '0')) : (input[_yearStart] - '0') * 1000 + (input[_yearStart + 1] - '0') * 100 + (input[_yearStart + 2] - '0') * 10 + (input[_yearStart + 3] - '0')); - var months = _monthStart == -1 ? DateTime.Now.Month : (_isMonthTwoChars ? (input[_monthStart] - '0') * 10 + (input[_monthStart + 1] - '0') : input[_monthStart] - '0'); - var days = _dayStart == -1 ? DateTime.Now.Day : (_isDayTwoChars ? (input[_dayStart] - '0') * 10 + (input[_dayStart + 1] - '0') : input[_dayStart] - '0'); + var len = part.Key - prevIndex; + parts2.Add(Tuple.Create(Parts.String, template.Substring(prevIndex, len))); + prevIndex += len; + } - var hours = _hourStart == -1 ? 0 : (input[_hourStart] - '0') * 10 + (input[_hourStart + 1] - '0'); - var minutes = _minuteStart == -1 ? 0 : (input[_minuteStart] - '0') * 10 + (input[_minuteStart + 1] - '0'); - var seconds = _secondStart == -1 ? 0 : (input[_secondStart] - '0') * 10 + (input[_secondStart + 1] - '0'); + parts2.Add(Tuple.Create(part.Value, (string)null)); - var millis = _milliStart == -1 ? 0 : (input[_milliStart] - '0') * 100 + (input[_milliStart + 1] - '0') * 10 + (input[_milliStart + 2] - '0'); - var micro = _microStart == -1 ? 0 : (input[_microStart] - '0') * 100 + (input[_microStart + 1] - '0') * 10 + (input[_microStart + 2] - '0'); - var nano = _nanoStart == -1 ? 0 : (input[_nanoStart] - '0') * 100 + (input[_nanoStart + 1] - '0') * 10 + (input[_nanoStart + 2] - '0'); + prevIndex += part.Value switch + { + Parts.Year => _isYearTwoChars ? 2 : 4, + Parts.Month => _isMonthTwoChars ? 2 : 1, + Parts.Day => _isDayTwoChars ? 2 : 1, + Parts.Mls => 3, + Parts.Mcs => 3, + Parts.Nano => 3, + _ => 2, + }; + } - var dt = new DateTime(years, months, days, hours, minutes, seconds, millis); + _parts = [.. parts2]; - if (micro > 0) - { - dt = dt.AddMicroseconds(micro); + //TimeHelper.InitBounds(template, 'y', out _yearStart, out _yearLen); + //TimeHelper.InitBounds(template, 'M', out _monthStart, out _monthLen); + //TimeHelper.InitBounds(template, 'd', out _dayStart, out _dayLen); + //TimeHelper.InitBounds(template, 'H', out _hourStart, out _hourLen); + //TimeHelper.InitBounds(template, 'm', out _minuteStart, out _minuteLen); + //TimeHelper.InitBounds(template, 's', out _secondStart, out _secondLen); + //TimeHelper.InitBounds(template, 'f', out _milliStart, out _milliLen); + } - if (nano > 0) - dt = dt.AddNanoseconds(nano); - } + /// + /// Parses the specified input string into a based on the predefined template. + /// + /// The string input representing the date and time. + /// A object parsed from the input string. + /// Thrown when the input cannot be parsed into a valid . + public DateTime Parse(string input) + { + try + { + //fixed (char* stringBuffer = input) + //{ + var years = _yearStart == -1 ? DateTime.Now.Year : (_isYearTwoChars ? ((DateTime.Now.Year / 1000) * 1000 + (input[_yearStart] - '0') * 10 + (input[_yearStart + 1] - '0')) : (input[_yearStart] - '0') * 1000 + (input[_yearStart + 1] - '0') * 100 + (input[_yearStart + 2] - '0') * 10 + (input[_yearStart + 3] - '0')); + var months = _monthStart == -1 ? DateTime.Now.Month : (_isMonthTwoChars ? (input[_monthStart] - '0') * 10 + (input[_monthStart + 1] - '0') : input[_monthStart] - '0'); + var days = _dayStart == -1 ? DateTime.Now.Day : (_isDayTwoChars ? (input[_dayStart] - '0') * 10 + (input[_dayStart + 1] - '0') : input[_dayStart] - '0'); - return dt; - //} - } - catch (Exception ex) - { - throw new InvalidCastException($"Cannot convert {input} with format {Template} to {typeof(DateTime).Name}.", ex); - } - } + var hours = _hourStart == -1 ? 0 : (input[_hourStart] - '0') * 10 + (input[_hourStart + 1] - '0'); + var minutes = _minuteStart == -1 ? 0 : (input[_minuteStart] - '0') * 10 + (input[_minuteStart + 1] - '0'); + var seconds = _secondStart == -1 ? 0 : (input[_secondStart] - '0') * 10 + (input[_secondStart + 1] - '0'); - /// - /// Parses the specified input string into a based on the predefined template, - /// applying the time zone if specified. - /// - /// The string input representing the date and time with time zone. - /// A object parsed from the input string. - public DateTimeOffset ParseDto(string input) - { - var dt = Parse(input); + var millis = _milliStart == -1 ? 0 : (input[_milliStart] - '0') * 100 + (input[_milliStart + 1] - '0') * 10 + (input[_milliStart + 2] - '0'); + var micro = _microStart == -1 ? 0 : (input[_microStart] - '0') * 100 + (input[_microStart + 1] - '0') * 10 + (input[_microStart + 2] - '0'); + var nano = _nanoStart == -1 ? 0 : (input[_nanoStart] - '0') * 100 + (input[_nanoStart + 1] - '0') * 10 + (input[_nanoStart + 2] - '0'); - var timeZone = TimeSpan.Zero; + var dt = new DateTime(years, months, days, hours, minutes, seconds, millis); - if (_timeZoneStart != -1) + if (micro > 0) { - var pos = input[_timeZoneStart] == '+'; - var hours = (input[_timeZoneStart + 1] - '0') * 10 + (input[_timeZoneStart + 2] - '0'); - var minutes = (input[_timeZoneStart + 4] - '0') * 10 + (input[_timeZoneStart + 5] - '0'); + dt = dt.AddMicroseconds(micro); - timeZone = new TimeSpan(pos ? hours : -hours, minutes, 0); + if (nano > 0) + dt = dt.AddNanoseconds(nano); } - return dt.ApplyTimeZone(timeZone); + return dt; + //} } - - /// - /// Converts the specified to its string representation based on the predefined template. - /// - /// The value to format. - /// A string representation of the date and time. - public string ToString(DateTime value) + catch (Exception ex) { - var builder = new StringBuilder(); + throw new InvalidCastException($"Cannot convert {input} with format {Template} to {typeof(DateTime).Name}.", ex); + } + } - foreach (var part in _parts) - { - switch (part.Item1) - { - case Parts.String: - builder.Append(part.Item2); - break; - case Parts.Year: - Append(builder, _isYearTwoChars ? value.Year % 100 : value.Year, _isYearTwoChars ? 2 : 4); - break; - case Parts.Month: - Append(builder, value.Month, _isMonthTwoChars ? 2 : 1); - break; - case Parts.Day: - Append(builder, value.Day, _isDayTwoChars ? 2 : 1); - break; - case Parts.Hour: - Append(builder, value.Hour); - break; - case Parts.Minute: - Append(builder, value.Minute); - break; - case Parts.Second: - Append(builder, value.Second); - break; - case Parts.Mls: - Append(builder, value.Millisecond, 3); - break; - case Parts.Mcs: - Append(builder, value.GetMicroseconds(), 3); - break; - case Parts.Nano: - Append(builder, value.GetNanoseconds(), 3); - break; - default: - throw new ArgumentOutOfRangeException(); - } - } + /// + /// Parses the specified input string into a based on the predefined template, + /// applying the time zone if specified. + /// + /// The string input representing the date and time with time zone. + /// A object parsed from the input string. + public DateTimeOffset ParseDto(string input) + { + var dt = Parse(input); + + var timeZone = TimeSpan.Zero; - return builder.ToString(); + if (_timeZoneStart != -1) + { + var pos = input[_timeZoneStart] == '+'; + var hours = (input[_timeZoneStart + 1] - '0') * 10 + (input[_timeZoneStart + 2] - '0'); + var minutes = (input[_timeZoneStart + 4] - '0') * 10 + (input[_timeZoneStart + 5] - '0'); + + timeZone = new TimeSpan(pos ? hours : -hours, minutes, 0); } - /// - /// Appends the specified integer value to the with a given size. - /// - /// The to append to. - /// The integer value to append. - /// The number of digits to include. - private static void Append(StringBuilder builder, int value, int size = 2) + return dt.ApplyTimeZone(timeZone); + } + + /// + /// Converts the specified to its string representation based on the predefined template. + /// + /// The value to format. + /// A string representation of the date and time. + public string ToString(DateTime value) + { + var builder = new StringBuilder(); + + foreach (var part in _parts) { - if (size == 1) + switch (part.Item1) { - builder.Append((char)(value + '0')); - } - else if (size == 2) - { - builder.Append((char)(value / 10 + '0')); - builder.Append((char)(value % 10 + '0')); - } - else if (size == 3) - { - builder.Append((char)(value / 100 + '0')); - builder.Append((char)((value - (value / 100) * 100) / 10 + '0')); - builder.Append((char)(value % 10 + '0')); - } - else - { - builder.Append((char)(value / 1000 + '0')); - value -= (value / 1000) * 1000; - builder.Append((char)(value / 100 + '0')); - value -= (value / 100) * 100; - builder.Append((char)(value / 10 + '0')); - builder.Append((char)(value % 10 + '0')); + case Parts.String: + builder.Append(part.Item2); + break; + case Parts.Year: + Append(builder, _isYearTwoChars ? value.Year % 100 : value.Year, _isYearTwoChars ? 2 : 4); + break; + case Parts.Month: + Append(builder, value.Month, _isMonthTwoChars ? 2 : 1); + break; + case Parts.Day: + Append(builder, value.Day, _isDayTwoChars ? 2 : 1); + break; + case Parts.Hour: + Append(builder, value.Hour); + break; + case Parts.Minute: + Append(builder, value.Minute); + break; + case Parts.Second: + Append(builder, value.Second); + break; + case Parts.Mls: + Append(builder, value.Millisecond, 3); + break; + case Parts.Mcs: + Append(builder, value.GetMicroseconds(), 3); + break; + case Parts.Nano: + Append(builder, value.GetNanoseconds(), 3); + break; + default: + throw new ArgumentOutOfRangeException(); } } - /// - public override string ToString() => Template; + return builder.ToString(); + } + + /// + /// Appends the specified integer value to the with a given size. + /// + /// The to append to. + /// The integer value to append. + /// The number of digits to include. + private static void Append(StringBuilder builder, int value, int size = 2) + { + if (size == 1) + { + builder.Append((char)(value + '0')); + } + else if (size == 2) + { + builder.Append((char)(value / 10 + '0')); + builder.Append((char)(value % 10 + '0')); + } + else if (size == 3) + { + builder.Append((char)(value / 100 + '0')); + builder.Append((char)((value - (value / 100) * 100) / 10 + '0')); + builder.Append((char)(value % 10 + '0')); + } + else + { + builder.Append((char)(value / 1000 + '0')); + value -= (value / 1000) * 1000; + builder.Append((char)(value / 100 + '0')); + value -= (value / 100) * 100; + builder.Append((char)(value / 10 + '0')); + builder.Append((char)(value % 10 + '0')); + } } + + /// + public override string ToString() => Template; } \ No newline at end of file diff --git a/Common/FastTimeSpanParser.cs b/Common/FastTimeSpanParser.cs index 7caa0cdf..72afd070 100644 --- a/Common/FastTimeSpanParser.cs +++ b/Common/FastTimeSpanParser.cs @@ -1,246 +1,245 @@ -namespace Ecng.Common +namespace Ecng.Common; + +using System; +using System.Collections.Generic; +using System.Text; + +/// +/// Provides high-performance parsing and formatting of TimeSpan values based on a specified template. +/// +public class FastTimeSpanParser { - using System; - using System.Collections.Generic; - using System.Text; + private enum Parts + { + String, + Day, + Hour, + Minute, + Second, + Mls, + Mcs, + Nano, + } + + private readonly Tuple[] _parts; + + private readonly int _dayStart; + private readonly int _hourStart; + private readonly int _minuteStart; + private readonly int _secondStart; + private readonly int _milliStart; + private readonly int _microStart; + private readonly int _nanoStart; /// - /// Provides high-performance parsing and formatting of TimeSpan values based on a specified template. + /// Gets the template string used for parsing and formatting TimeSpan values. /// - public class FastTimeSpanParser + public string Template { get; } + + /// + /// Initializes a new instance of the class using the specified template. + /// + /// A format template that defines how TimeSpan values are parsed and formatted. Must not be null or empty. + /// Thrown when the provided template is null or empty. + public FastTimeSpanParser(string template) { - private enum Parts - { - String, - Day, - Hour, - Minute, - Second, - Mls, - Mcs, - Nano, - } + if (template.IsEmpty()) + throw new ArgumentNullException(nameof(template)); - private readonly Tuple[] _parts; - - private readonly int _dayStart; - private readonly int _hourStart; - private readonly int _minuteStart; - private readonly int _secondStart; - private readonly int _milliStart; - private readonly int _microStart; - private readonly int _nanoStart; - - /// - /// Gets the template string used for parsing and formatting TimeSpan values. - /// - public string Template { get; } - - /// - /// Initializes a new instance of the class using the specified template. - /// - /// A format template that defines how TimeSpan values are parsed and formatted. Must not be null or empty. - /// Thrown when the provided template is null or empty. - public FastTimeSpanParser(string template) - { - if (template.IsEmpty()) - throw new ArgumentNullException(nameof(template)); + Template = template; - Template = template; + template = template.Remove("\\"); - template = template.Remove("\\"); + _dayStart = template.IndexOf('d'); + _hourStart = template.IndexOf('h'); + _minuteStart = template.IndexOf('m'); + _secondStart = template.IndexOf('s'); + _milliStart = template.IndexOf('f'); - _dayStart = template.IndexOf('d'); - _hourStart = template.IndexOf('h'); - _minuteStart = template.IndexOf('m'); - _secondStart = template.IndexOf('s'); - _milliStart = template.IndexOf('f'); + if (_dayStart == -1) + _dayStart = template.IndexOf('D'); - if (_dayStart == -1) - _dayStart = template.IndexOf('D'); + if (_hourStart == -1) + _hourStart = template.IndexOf('H'); - if (_hourStart == -1) - _hourStart = template.IndexOf('H'); + if (_minuteStart == -1) + _minuteStart = template.IndexOf('M'); - if (_minuteStart == -1) - _minuteStart = template.IndexOf('M'); + if (_secondStart == -1) + _secondStart = template.IndexOf('S'); - if (_secondStart == -1) - _secondStart = template.IndexOf('S'); + if (_milliStart == -1) + _milliStart = template.IndexOf('F'); - if (_milliStart == -1) - _milliStart = template.IndexOf('F'); + var parts = new SortedList(); - var parts = new SortedList(); + if (_dayStart != -1) + parts.Add(_dayStart, Parts.Day); - if (_dayStart != -1) - parts.Add(_dayStart, Parts.Day); + if (_hourStart != -1) + parts.Add(_hourStart, Parts.Hour); - if (_hourStart != -1) - parts.Add(_hourStart, Parts.Hour); + if (_minuteStart != -1) + parts.Add(_minuteStart, Parts.Minute); - if (_minuteStart != -1) - parts.Add(_minuteStart, Parts.Minute); + if (_secondStart != -1) + parts.Add(_secondStart, Parts.Second); - if (_secondStart != -1) - parts.Add(_secondStart, Parts.Second); + _microStart = -1; + _nanoStart = -1; - _microStart = -1; - _nanoStart = -1; + if (_milliStart != -1) + { + parts.Add(_milliStart, Parts.Mls); + + var microStart = _milliStart + 3; - if (_milliStart != -1) + if (template.Length > (microStart + 1) && (template[microStart] == 'f' || template[microStart] == 'F')) { - parts.Add(_milliStart, Parts.Mls); + _microStart = microStart; + parts.Add(_microStart, Parts.Mcs); - var microStart = _milliStart + 3; + var nanoStart = _microStart + 3; - if (template.Length > (microStart + 1) && (template[microStart] == 'f' || template[microStart] == 'F')) + if (template.Length > (nanoStart + 1) && (template[nanoStart] == 'f' || template[nanoStart] == 'F')) { - _microStart = microStart; - parts.Add(_microStart, Parts.Mcs); - - var nanoStart = _microStart + 3; - - if (template.Length > (nanoStart + 1) && (template[nanoStart] == 'f' || template[nanoStart] == 'F')) - { - _nanoStart = nanoStart; - parts.Add(_nanoStart, Parts.Nano); - } + _nanoStart = nanoStart; + parts.Add(_nanoStart, Parts.Nano); } } + } - var parts2 = new List>(); + var parts2 = new List>(); - var prevIndex = 0; + var prevIndex = 0; - foreach (var part in parts) + foreach (var part in parts) + { + if (prevIndex != part.Key) { - if (prevIndex != part.Key) - { - var len = part.Key - prevIndex; - parts2.Add(Tuple.Create(Parts.String, template.Substring(prevIndex, len))); - prevIndex += len; - } - - parts2.Add(Tuple.Create(part.Value, (string)null)); - - prevIndex += part.Value switch - { - Parts.Mls => 3, - Parts.Mcs => 3, - Parts.Nano => 3, - _ => 2, - }; + var len = part.Key - prevIndex; + parts2.Add(Tuple.Create(Parts.String, template.Substring(prevIndex, len))); + prevIndex += len; } - _parts = [.. parts2]; + parts2.Add(Tuple.Create(part.Value, (string)null)); - //TimeHelper.InitBounds(template, 'd', out _dayStart, out _dayLen); - //TimeHelper.InitBounds(template, 'h', out _hourStart, out _hourLen); - //TimeHelper.InitBounds(template, 'm', out _minuteStart, out _minuteLen); - //TimeHelper.InitBounds(template, 's', out _secondStart, out _secondLen); - //TimeHelper.InitBounds(template, 'f', out _milliStart, out _milliLen); + prevIndex += part.Value switch + { + Parts.Mls => 3, + Parts.Mcs => 3, + Parts.Nano => 3, + _ => 2, + }; } - /// - /// Parses the specified input string into a value based on the parser's template. - /// - /// The string representation of a time span to parse. - /// A value that corresponds to the given input. - /// Thrown when the input cannot be converted to a TimeSpan using the provided template. - public TimeSpan Parse(string input) - { - try - { - var days = _dayStart == -1 ? 0 : (input[_dayStart] - '0') * 10 + (input[_dayStart + 1] - '0'); + _parts = [.. parts2]; - var hours = _hourStart == -1 ? 0 : (input[_hourStart] - '0') * 10 + (input[_hourStart + 1] - '0'); - var minutes = _minuteStart == -1 ? 0 : (input[_minuteStart] - '0') * 10 + (input[_minuteStart + 1] - '0'); - var seconds = _secondStart == -1 ? 0 : (input[_secondStart] - '0') * 10 + (input[_secondStart + 1] - '0'); + //TimeHelper.InitBounds(template, 'd', out _dayStart, out _dayLen); + //TimeHelper.InitBounds(template, 'h', out _hourStart, out _hourLen); + //TimeHelper.InitBounds(template, 'm', out _minuteStart, out _minuteLen); + //TimeHelper.InitBounds(template, 's', out _secondStart, out _secondLen); + //TimeHelper.InitBounds(template, 'f', out _milliStart, out _milliLen); + } - var millis = _milliStart == -1 ? 0 : (input[_milliStart] - '0') * 100 + (input[_milliStart + 1] - '0') * 10 + (input[_milliStart + 2] - '0'); - var micro = _microStart == -1 ? 0 : (input[_microStart] - '0') * 100 + (input[_microStart + 1] - '0') * 10 + (input[_microStart + 2] - '0'); - var nano = _nanoStart == -1 ? 0 : (input[_nanoStart] - '0') * 100 + (input[_nanoStart + 1] - '0') * 10 + (input[_nanoStart + 2] - '0'); + /// + /// Parses the specified input string into a value based on the parser's template. + /// + /// The string representation of a time span to parse. + /// A value that corresponds to the given input. + /// Thrown when the input cannot be converted to a TimeSpan using the provided template. + public TimeSpan Parse(string input) + { + try + { + var days = _dayStart == -1 ? 0 : (input[_dayStart] - '0') * 10 + (input[_dayStart + 1] - '0'); - var ts = new TimeSpan(days, hours, minutes, seconds, millis); + var hours = _hourStart == -1 ? 0 : (input[_hourStart] - '0') * 10 + (input[_hourStart + 1] - '0'); + var minutes = _minuteStart == -1 ? 0 : (input[_minuteStart] - '0') * 10 + (input[_minuteStart + 1] - '0'); + var seconds = _secondStart == -1 ? 0 : (input[_secondStart] - '0') * 10 + (input[_secondStart + 1] - '0'); - if (micro > 0) - { - ts = ts.AddMicroseconds(micro); + var millis = _milliStart == -1 ? 0 : (input[_milliStart] - '0') * 100 + (input[_milliStart + 1] - '0') * 10 + (input[_milliStart + 2] - '0'); + var micro = _microStart == -1 ? 0 : (input[_microStart] - '0') * 100 + (input[_microStart + 1] - '0') * 10 + (input[_microStart + 2] - '0'); + var nano = _nanoStart == -1 ? 0 : (input[_nanoStart] - '0') * 100 + (input[_nanoStart + 1] - '0') * 10 + (input[_nanoStart + 2] - '0'); - if (nano > 0) - ts = ts.AddNanoseconds(nano); - } + var ts = new TimeSpan(days, hours, minutes, seconds, millis); - return ts; - } - catch (Exception ex) + if (micro > 0) { - throw new InvalidCastException($"Cannot convert {input} with format {Template} to {typeof(TimeSpan).Name}.", ex); - } - } + ts = ts.AddMicroseconds(micro); - /// - /// Formats the specified value into a string using the parser's template. - /// - /// The value to format. - /// A string representation of the value according to the template. - public string ToString(TimeSpan value) - { - var builder = new StringBuilder(); - - foreach (var part in _parts) - { - switch (part.Item1) - { - case Parts.String: - builder.Append(part.Item2); - break; - case Parts.Day: - Append(builder, value.Days); - break; - case Parts.Hour: - Append(builder, value.Hours); - break; - case Parts.Minute: - Append(builder, value.Minutes); - break; - case Parts.Second: - Append(builder, value.Seconds); - break; - case Parts.Mls: - Append(builder, value.Milliseconds, 3); - break; - case Parts.Mcs: - Append(builder, value.GetMicroseconds(), 3); - break; - case Parts.Nano: - Append(builder, value.GetNanoseconds(), 3); - break; - default: - throw new ArgumentOutOfRangeException(); - } + if (nano > 0) + ts = ts.AddNanoseconds(nano); } - return builder.ToString(); + return ts; } + catch (Exception ex) + { + throw new InvalidCastException($"Cannot convert {input} with format {Template} to {typeof(TimeSpan).Name}.", ex); + } + } - private static void Append(StringBuilder builder, int value, int size = 2) + /// + /// Formats the specified value into a string using the parser's template. + /// + /// The value to format. + /// A string representation of the value according to the template. + public string ToString(TimeSpan value) + { + var builder = new StringBuilder(); + + foreach (var part in _parts) { - if (size == 2) + switch (part.Item1) { - builder.Append((char)(value / 10 + '0')); - builder.Append((char)(value % 10 + '0')); - } - else - { - builder.Append((char)(value / 100 + '0')); - builder.Append((char)((value - (value / 100) * 100) / 10 + '0')); - builder.Append((char)(value % 10 + '0')); + case Parts.String: + builder.Append(part.Item2); + break; + case Parts.Day: + Append(builder, value.Days); + break; + case Parts.Hour: + Append(builder, value.Hours); + break; + case Parts.Minute: + Append(builder, value.Minutes); + break; + case Parts.Second: + Append(builder, value.Seconds); + break; + case Parts.Mls: + Append(builder, value.Milliseconds, 3); + break; + case Parts.Mcs: + Append(builder, value.GetMicroseconds(), 3); + break; + case Parts.Nano: + Append(builder, value.GetNanoseconds(), 3); + break; + default: + throw new ArgumentOutOfRangeException(); } } - /// - public override string ToString() => Template; + return builder.ToString(); + } + + private static void Append(StringBuilder builder, int value, int size = 2) + { + if (size == 2) + { + builder.Append((char)(value / 10 + '0')); + builder.Append((char)(value % 10 + '0')); + } + else + { + builder.Append((char)(value / 100 + '0')); + builder.Append((char)((value - (value / 100) * 100) / 10 + '0')); + builder.Append((char)(value % 10 + '0')); + } } + + /// + public override string ToString() => Template; } \ No newline at end of file diff --git a/Common/ForbiddenException.cs b/Common/ForbiddenException.cs index 6f321339..5e7db170 100644 --- a/Common/ForbiddenException.cs +++ b/Common/ForbiddenException.cs @@ -1,12 +1,11 @@ -namespace Ecng.Common -{ - using System; +namespace Ecng.Common; + +using System; - /// - /// Exception thrown when an operation is forbidden. - /// - /// The error message that explains the reason for the exception. - public class ForbiddenException(string message) : InvalidOperationException(message) - { - } +/// +/// Exception thrown when an operation is forbidden. +/// +/// The error message that explains the reason for the exception. +public class ForbiddenException(string message) : InvalidOperationException(message) +{ } \ No newline at end of file diff --git a/Common/HexEncoding.cs b/Common/HexEncoding.cs index 6f19279f..3fbd195f 100644 --- a/Common/HexEncoding.cs +++ b/Common/HexEncoding.cs @@ -1,328 +1,327 @@ -namespace Ecng.Common -{ - using System; - using System.Text; +namespace Ecng.Common; + +using System; +using System.Text; +/// +/// Summary description for HexEncoding. +/// http://www.codeproject.com/KB/recipes/hexencoding.aspx +/// +public class HexEncoding : Encoding +{ /// - /// Summary description for HexEncoding. - /// http://www.codeproject.com/KB/recipes/hexencoding.aspx + /// When overridden in a derived class, calculates the number of bytes produced by encoding the characters in the specified . /// - public class HexEncoding : Encoding + /// + /// The number of bytes produced by encoding the specified characters. + /// + /// The containing the set of characters to encode. + /// is null. + /// A fallback occurred (see Understanding Encodings for complete explanation) + /// -and- + /// is set to . + /// + /// 1 + public override int GetByteCount(string hexString) { - /// - /// When overridden in a derived class, calculates the number of bytes produced by encoding the characters in the specified . - /// - /// - /// The number of bytes produced by encoding the specified characters. - /// - /// The containing the set of characters to encode. - /// is null. - /// A fallback occurred (see Understanding Encodings for complete explanation) - /// -and- - /// is set to . - /// - /// 1 - public override int GetByteCount(string hexString) - { - if (hexString.IsEmpty()) - throw new ArgumentNullException(nameof(hexString)); + if (hexString.IsEmpty()) + throw new ArgumentNullException(nameof(hexString)); - return GetByteCount(hexString.ToCharArray(), 0, hexString.Length); - } - - /// - /// When overridden in a derived class, calculates the number of bytes produced by encoding a set of characters from the specified character array. - /// - /// - /// The number of bytes produced by encoding the specified characters. - /// - /// The character array containing the set of characters to encode. - /// The index of the first character to encode. - /// The number of characters to encode. - /// is null. - /// or is less than zero. - /// -or- - /// and do not denote a valid range in . - /// - /// A fallback occurred (see Understanding Encodings for complete explanation) - /// -and- - /// is set to . - /// - /// 1 - public override int GetByteCount(char[] chars, int index, int count) - { - if (chars is null) - throw new ArgumentNullException(nameof(chars)); - - if (chars.Length < (index + count)) - throw new ArgumentOutOfRangeException(nameof(chars)); + return GetByteCount(hexString.ToCharArray(), 0, hexString.Length); + } - var numHexChars = 0; + /// + /// When overridden in a derived class, calculates the number of bytes produced by encoding a set of characters from the specified character array. + /// + /// + /// The number of bytes produced by encoding the specified characters. + /// + /// The character array containing the set of characters to encode. + /// The index of the first character to encode. + /// The number of characters to encode. + /// is null. + /// or is less than zero. + /// -or- + /// and do not denote a valid range in . + /// + /// A fallback occurred (see Understanding Encodings for complete explanation) + /// -and- + /// is set to . + /// + /// 1 + public override int GetByteCount(char[] chars, int index, int count) + { + if (chars is null) + throw new ArgumentNullException(nameof(chars)); - // remove all none A-F, 0-9, characters - for (int i = index; i < index + count; i++) - { - var c = chars[i]; + if (chars.Length < (index + count)) + throw new ArgumentOutOfRangeException(nameof(chars)); - if (IsHexDigit(c)) - numHexChars++; - } + var numHexChars = 0; - // if odd number of characters, discard last character - if (numHexChars % 2 != 0) - { - numHexChars--; - } + // remove all none A-F, 0-9, characters + for (int i = index; i < index + count; i++) + { + var c = chars[i]; - return numHexChars / 2; // 2 characters per byte + if (IsHexDigit(c)) + numHexChars++; } - /// - /// When overridden in a derived class, encodes a set of characters from the specified character array into the specified byte array. - /// - /// - /// The actual number of bytes written into . - /// - /// The character array containing the set of characters to encode. - /// The index of the first character to encode. - /// The number of characters to encode. - /// The byte array to contain the resulting sequence of bytes. - /// The index at which to start writing the resulting sequence of bytes. - /// is null. - /// -or- - /// is null. - /// - /// - /// or or is less than zero. - /// -or- - /// and do not denote a valid range in . - /// -or- - /// is not a valid index in . - /// - /// does not have enough capacity from to the end of the array to accommodate the resulting bytes. - /// - /// A fallback occurred (see Understanding Encodings for complete explanation) - /// -and- - /// is set to . - /// - /// 1 - public override int GetBytes(char[] chars, int charIndex, int charCount, byte[] bytes, int byteIndex) + // if odd number of characters, discard last character + if (numHexChars % 2 != 0) { - var hexBytes = GetBytes(chars, charIndex, charCount, out int discarded); - Array.Copy(hexBytes, 0, bytes, byteIndex, hexBytes.Length); - return hexBytes.Length; + numHexChars--; } - /// - /// Creates a byte array from the hexadecimal string. Each two characters are combined - /// to create one byte. First two hexadecimal characters become first byte in returned array. - /// Non-hexadecimal characters are ignored. - /// - /// string to convert to byte array - /// byte array, in the same left-to-right order as the hexString - public override byte[] GetBytes(string hexString) - { - if (hexString.IsEmpty()) - throw new ArgumentNullException(nameof(hexString)); + return numHexChars / 2; // 2 characters per byte + } - return GetBytes(hexString.ToCharArray(), 0, hexString.Length, out int discarded); - } + /// + /// When overridden in a derived class, encodes a set of characters from the specified character array into the specified byte array. + /// + /// + /// The actual number of bytes written into . + /// + /// The character array containing the set of characters to encode. + /// The index of the first character to encode. + /// The number of characters to encode. + /// The byte array to contain the resulting sequence of bytes. + /// The index at which to start writing the resulting sequence of bytes. + /// is null. + /// -or- + /// is null. + /// + /// + /// or or is less than zero. + /// -or- + /// and do not denote a valid range in . + /// -or- + /// is not a valid index in . + /// + /// does not have enough capacity from to the end of the array to accommodate the resulting bytes. + /// + /// A fallback occurred (see Understanding Encodings for complete explanation) + /// -and- + /// is set to . + /// + /// 1 + public override int GetBytes(char[] chars, int charIndex, int charCount, byte[] bytes, int byteIndex) + { + var hexBytes = GetBytes(chars, charIndex, charCount, out int discarded); + Array.Copy(hexBytes, 0, bytes, byteIndex, hexBytes.Length); + return hexBytes.Length; + } - /// - /// When overridden in a derived class, calculates the number of characters produced by decoding a sequence of bytes from the specified byte array. - /// - /// - /// The number of characters produced by decoding the specified sequence of bytes. - /// - /// The byte array containing the sequence of bytes to decode. - /// The index of the first byte to decode. - /// The number of bytes to decode. - /// is null. - /// or is less than zero. - /// -or- - /// and do not denote a valid range in . - /// A fallback occurred (see Understanding Encodings for complete explanation) - /// -and- - /// is set to . - /// - /// 1 - public override int GetCharCount(byte[] bytes, int index, int count) - { - var charCount = 0; + /// + /// Creates a byte array from the hexadecimal string. Each two characters are combined + /// to create one byte. First two hexadecimal characters become first byte in returned array. + /// Non-hexadecimal characters are ignored. + /// + /// string to convert to byte array + /// byte array, in the same left-to-right order as the hexString + public override byte[] GetBytes(string hexString) + { + if (hexString.IsEmpty()) + throw new ArgumentNullException(nameof(hexString)); - for (var i = index; i < (index + count); i++) - charCount += bytes[i].ToString("X2").Length; + return GetBytes(hexString.ToCharArray(), 0, hexString.Length, out int discarded); + } - return charCount; - } + /// + /// When overridden in a derived class, calculates the number of characters produced by decoding a sequence of bytes from the specified byte array. + /// + /// + /// The number of characters produced by decoding the specified sequence of bytes. + /// + /// The byte array containing the sequence of bytes to decode. + /// The index of the first byte to decode. + /// The number of bytes to decode. + /// is null. + /// or is less than zero. + /// -or- + /// and do not denote a valid range in . + /// A fallback occurred (see Understanding Encodings for complete explanation) + /// -and- + /// is set to . + /// + /// 1 + public override int GetCharCount(byte[] bytes, int index, int count) + { + var charCount = 0; - /// - /// When overridden in a derived class, decodes a sequence of bytes from the specified byte array into the specified character array. - /// - /// - /// The actual number of characters written into . - /// - /// The byte array containing the sequence of bytes to decode. - /// The index of the first byte to decode. - /// The number of bytes to decode. - /// The character array to contain the resulting set of characters. - /// The index at which to start writing the resulting set of characters. - /// is null. - /// -or- - /// is null. - /// or or is less than zero. - /// -or- - /// and do not denote a valid range in . - /// -or- - /// is not a valid index in . - /// - /// does not have enough capacity from to the end of the array to accommodate the resulting characters. - /// - /// A fallback occurred (see Understanding Encodings for complete explanation) - /// -and- - /// is set to . - /// - /// 1 - public override int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex) - { - var result = new StringBuilder(); + for (var i = index; i < (index + count); i++) + charCount += bytes[i].ToString("X2").Length; - for (var i = byteIndex; i < (byteIndex + byteCount); i++) - result.AppendFormat(bytes[i].ToString("X2")); + return charCount; + } - Array.Copy(result.ToString().ToCharArray(), 0, chars, charIndex, result.Length); - return result.Length; - } + /// + /// When overridden in a derived class, decodes a sequence of bytes from the specified byte array into the specified character array. + /// + /// + /// The actual number of characters written into . + /// + /// The byte array containing the sequence of bytes to decode. + /// The index of the first byte to decode. + /// The number of bytes to decode. + /// The character array to contain the resulting set of characters. + /// The index at which to start writing the resulting set of characters. + /// is null. + /// -or- + /// is null. + /// or or is less than zero. + /// -or- + /// and do not denote a valid range in . + /// -or- + /// is not a valid index in . + /// + /// does not have enough capacity from to the end of the array to accommodate the resulting characters. + /// + /// A fallback occurred (see Understanding Encodings for complete explanation) + /// -and- + /// is set to . + /// + /// 1 + public override int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex) + { + var result = new StringBuilder(); - /// - /// When overridden in a derived class, calculates the maximum number of bytes produced by encoding the specified number of characters. - /// - /// - /// The maximum number of bytes produced by encoding the specified number of characters. - /// - /// The number of characters to encode. - /// is less than zero. - /// A fallback occurred (see Understanding Encodings for complete explanation) - /// -and- - /// is set to . - /// - /// 1 - public override int GetMaxByteCount(int charCount) - { - return charCount * 2; - } + for (var i = byteIndex; i < (byteIndex + byteCount); i++) + result.AppendFormat(bytes[i].ToString("X2")); - /// - /// When overridden in a derived class, calculates the maximum number of characters produced by decoding the specified number of bytes. - /// - /// - /// The maximum number of characters produced by decoding the specified number of bytes. - /// - /// The number of bytes to decode. - /// is less than zero. - /// A fallback occurred (see Understanding Encodings for complete explanation) - /// -and- - /// is set to . - /// - /// 1 - public override int GetMaxCharCount(int byteCount) - { - if (byteCount % 2 != 0) - { - byteCount--; - } + Array.Copy(result.ToString().ToCharArray(), 0, chars, charIndex, result.Length); + return result.Length; + } - return byteCount / 2; - } + /// + /// When overridden in a derived class, calculates the maximum number of bytes produced by encoding the specified number of characters. + /// + /// + /// The maximum number of bytes produced by encoding the specified number of characters. + /// + /// The number of characters to encode. + /// is less than zero. + /// A fallback occurred (see Understanding Encodings for complete explanation) + /// -and- + /// is set to . + /// + /// 1 + public override int GetMaxByteCount(int charCount) + { + return charCount * 2; + } - /// - /// Creates a byte array from the hexadecimal string. Each two characters are combined - /// to create one byte. First two hexadecimal characters become first byte in returned array. - /// Non-hexadecimal characters are ignored. - /// - /// The character array containing the set of characters to encode. - /// The index of the first character to encode. - /// The number of characters to encode. - /// number of characters in string ignored. - /// byte array, in the same left-to-right order as the hexString. - public static byte[] GetBytes(char[] chars, int charIndex, int charCount, out int discarded) + /// + /// When overridden in a derived class, calculates the maximum number of characters produced by decoding the specified number of bytes. + /// + /// + /// The maximum number of characters produced by decoding the specified number of bytes. + /// + /// The number of bytes to decode. + /// is less than zero. + /// A fallback occurred (see Understanding Encodings for complete explanation) + /// -and- + /// is set to . + /// + /// 1 + public override int GetMaxCharCount(int byteCount) + { + if (byteCount % 2 != 0) { - if (chars is null) - throw new ArgumentNullException(nameof(chars)); + byteCount--; + } - if (chars.Length < (charIndex + charCount)) - throw new ArgumentOutOfRangeException(nameof(chars)); + return byteCount / 2; + } + /// + /// Creates a byte array from the hexadecimal string. Each two characters are combined + /// to create one byte. First two hexadecimal characters become first byte in returned array. + /// Non-hexadecimal characters are ignored. + /// + /// The character array containing the set of characters to encode. + /// The index of the first character to encode. + /// The number of characters to encode. + /// number of characters in string ignored. + /// byte array, in the same left-to-right order as the hexString. + public static byte[] GetBytes(char[] chars, int charIndex, int charCount, out int discarded) + { + if (chars is null) + throw new ArgumentNullException(nameof(chars)); - discarded = 0; - var newString = string.Empty; + if (chars.Length < (charIndex + charCount)) + throw new ArgumentOutOfRangeException(nameof(chars)); - // remove all none A-F, 0-9, characters - for (var i = charIndex; i < (charIndex + charCount); i++) - { - var c = chars[i]; - if (IsHexDigit(c)) - newString += c; - else - discarded++; - } + discarded = 0; + var newString = string.Empty; + + // remove all none A-F, 0-9, characters + for (var i = charIndex; i < (charIndex + charCount); i++) + { + var c = chars[i]; - // if odd number of characters, discard last character - if (newString.Length % 2 != 0) - { + if (IsHexDigit(c)) + newString += c; + else discarded++; - newString = newString.Substring(0, newString.Length - 1); - } - - var byteLength = newString.Length / 2; - var bytes = new byte[byteLength]; - var j = 0; - for (var i = 0; i < bytes.Length; i++) - { - var hex = new string([newString[j], newString[j + 1]]); - bytes[i] = HexToByte(hex); - j = j + 2; - } - - return bytes; } - /// - /// Returns true is c is a hexadecimal digit (A-F, a-f, 0-9) - /// - /// Character to test. - /// true if hex digit, false if not. - public static bool IsHexDigit(char c) + // if odd number of characters, discard last character + if (newString.Length % 2 != 0) { - var numA = 'A'.To(); - var num1 = '0'.To(); + discarded++; + newString = newString.Substring(0, newString.Length - 1); + } - c = c.ToUpper(false); + var byteLength = newString.Length / 2; + var bytes = new byte[byteLength]; + var j = 0; + for (var i = 0; i < bytes.Length; i++) + { + var hex = new string([newString[j], newString[j + 1]]); + bytes[i] = HexToByte(hex); + j = j + 2; + } - var numChar = c.To(); - if (numChar >= numA && numChar < (numA + 6)) - return true; - if (numChar >= num1 && numChar < (num1 + 10)) - return true; + return bytes; + } - return false; - } + /// + /// Returns true is c is a hexadecimal digit (A-F, a-f, 0-9) + /// + /// Character to test. + /// true if hex digit, false if not. + public static bool IsHexDigit(char c) + { + var numA = 'A'.To(); + var num1 = '0'.To(); - /// - /// Converts 1 or 2 character string into equivalant byte value - /// - /// 1 or 2 character string. - /// byte - private static byte HexToByte(string hexString) - { - if (hexString.IsEmpty()) - throw new ArgumentNullException(nameof(hexString)); + c = c.ToUpper(false); - if (hexString.Length > 2 || hexString.Length <= 0) - throw new ArgumentException("hex must be 1 or 2 characters in length"); + var numChar = c.To(); + if (numChar >= numA && numChar < (numA + 6)) + return true; + if (numChar >= num1 && numChar < (num1 + 10)) + return true; - return byte.Parse(hexString, System.Globalization.NumberStyles.HexNumber); - } + return false; + } + + /// + /// Converts 1 or 2 character string into equivalant byte value + /// + /// 1 or 2 character string. + /// byte + private static byte HexToByte(string hexString) + { + if (hexString.IsEmpty()) + throw new ArgumentNullException(nameof(hexString)); + + if (hexString.Length > 2 || hexString.Length <= 0) + throw new ArgumentException("hex must be 1 or 2 characters in length"); + + return byte.Parse(hexString, System.Globalization.NumberStyles.HexNumber); } } \ No newline at end of file diff --git a/Common/ICloneable.cs b/Common/ICloneable.cs index cc19d293..2063cf36 100644 --- a/Common/ICloneable.cs +++ b/Common/ICloneable.cs @@ -1,21 +1,20 @@ -namespace Ecng.Common -{ - #region Using Directives +namespace Ecng.Common; + +#region Using Directives - using System; +using System; - #endregion +#endregion +/// +/// Defines a method that creates a new object that is a deep copy of the current instance. +/// +/// The type of the object that is cloned. +public interface ICloneable : ICloneable +{ /// - /// Defines a method that creates a new object that is a deep copy of the current instance. + /// Creates a new object that is a deep copy of the current instance. /// - /// The type of the object that is cloned. - public interface ICloneable : ICloneable - { - /// - /// Creates a new object that is a deep copy of the current instance. - /// - /// A new object that is a deep copy of this instance. - new T Clone(); - } + /// A new object that is a deep copy of this instance. + new T Clone(); } \ No newline at end of file diff --git a/Common/IOHelper.cs b/Common/IOHelper.cs index d4b3ce46..7ffdd1a1 100644 --- a/Common/IOHelper.cs +++ b/Common/IOHelper.cs @@ -1,1228 +1,1227 @@ -namespace Ecng.Common +namespace Ecng.Common; + +using System; +using System.Threading; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Text; +using System.Linq; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.ComponentModel; +using System.Runtime.InteropServices; +using System.Globalization; + +using Nito.AsyncEx; + +/// +/// Provides helper methods for file and directory operations. +/// +public static class IOHelper { - using System; - using System.Threading; - using System.Threading.Tasks; - using System.Collections.Generic; - using System.Text; - using System.Linq; - using System.Diagnostics; - using System.IO; - using System.Reflection; - using System.ComponentModel; - using System.Runtime.InteropServices; - using System.Globalization; - - using Nito.AsyncEx; + /// + /// Clears the specified directory by deleting its files and subdirectories. + /// + /// The directory path to clear. + /// Optional filter to determine which files to delete. + /// A DirectoryInfo for the cleared directory. + public static DirectoryInfo ClearDirectory(string path, Func filter = null) + => AsyncContext.Run(() => ClearDirectoryAsync(path, filter)); /// - /// Provides helper methods for file and directory operations. + /// Asynchronously clears the specified directory by deleting its files and subdirectories. /// - public static class IOHelper + /// The directory path to clear. + /// Optional filter to determine which files to delete. + /// A cancellation token. + /// A task that represents the asynchronous operation, containing a DirectoryInfo for the cleared directory. + public static Task ClearDirectoryAsync(string path, Func filter = null, CancellationToken cancellationToken = default) { - /// - /// Clears the specified directory by deleting its files and subdirectories. - /// - /// The directory path to clear. - /// Optional filter to determine which files to delete. - /// A DirectoryInfo for the cleared directory. - public static DirectoryInfo ClearDirectory(string path, Func filter = null) - => AsyncContext.Run(() => ClearDirectoryAsync(path, filter)); - - /// - /// Asynchronously clears the specified directory by deleting its files and subdirectories. - /// - /// The directory path to clear. - /// Optional filter to determine which files to delete. - /// A cancellation token. - /// A task that represents the asynchronous operation, containing a DirectoryInfo for the cleared directory. - public static Task ClearDirectoryAsync(string path, Func filter = null, CancellationToken cancellationToken = default) - { - var parentDir = new DirectoryInfo(path); - - foreach (var file in parentDir.EnumerateFiles()) - { - if (filter != null && !filter(file.FullName)) - continue; - - file.Delete(); - - cancellationToken.ThrowIfCancellationRequested(); - } + var parentDir = new DirectoryInfo(path); - foreach (var dir in parentDir.EnumerateDirectories()) - { - dir.Delete(true); + foreach (var file in parentDir.EnumerateFiles()) + { + if (filter != null && !filter(file.FullName)) + continue; - cancellationToken.ThrowIfCancellationRequested(); - } + file.Delete(); - return parentDir.FromResult(); + cancellationToken.ThrowIfCancellationRequested(); } - /// - /// Copies the content of one directory to another. - /// - /// The source directory path. - /// The destination directory path. - public static void CopyDirectory(string sourcePath, string destPath) - => AsyncContext.Run(() => CopyDirectoryAsync(sourcePath, destPath)); - - /// - /// Asynchronously copies the content of one directory to another. - /// - /// The source directory path. - /// The destination directory path. - /// A cancellation token. - /// A task that represents the asynchronous operation. - public static async Task CopyDirectoryAsync(string sourcePath, string destPath, CancellationToken cancellationToken = default) + foreach (var dir in parentDir.EnumerateDirectories()) { - Directory.CreateDirectory(destPath); - - foreach (var fileName in Directory.GetFiles(sourcePath)) - { - CopyAndMakeWritable(fileName, destPath); + dir.Delete(true); - cancellationToken.ThrowIfCancellationRequested(); - } - - foreach (var directory in Directory.GetDirectories(sourcePath)) - { - await CopyDirectoryAsync(directory, Path.Combine(destPath, Path.GetFileName(directory)), cancellationToken); - } + cancellationToken.ThrowIfCancellationRequested(); } - /// - /// Copies a file to the specified destination and makes the copy writable. - /// - /// The source file path. - /// The destination directory path. - /// The destination file path. - public static string CopyAndMakeWritable(string fileName, string destPath) - { - var destFile = Path.Combine(destPath, Path.GetFileName(fileName)); + return parentDir.FromResult(); + } - File.Copy(fileName, destFile, true); - new FileInfo(destFile).IsReadOnly = false; + /// + /// Copies the content of one directory to another. + /// + /// The source directory path. + /// The destination directory path. + public static void CopyDirectory(string sourcePath, string destPath) + => AsyncContext.Run(() => CopyDirectoryAsync(sourcePath, destPath)); - return destFile; - } + /// + /// Asynchronously copies the content of one directory to another. + /// + /// The source directory path. + /// The destination directory path. + /// A cancellation token. + /// A task that represents the asynchronous operation. + public static async Task CopyDirectoryAsync(string sourcePath, string destPath, CancellationToken cancellationToken = default) + { + Directory.CreateDirectory(destPath); - /// - /// Converts a relative or partial path to a fully qualified path. - /// - /// The input path. - /// The absolute path. - public static string ToFullPath(this string path) + foreach (var fileName in Directory.GetFiles(sourcePath)) { - if (path is null) - throw new ArgumentNullException(nameof(path)); + CopyAndMakeWritable(fileName, destPath); - return Path.GetFullPath(path); + cancellationToken.ThrowIfCancellationRequested(); } - /// - /// Adds a relative segment to the current path and returns the fully qualified path. - /// - /// The base path. - /// The relative segment to add. - /// The combined full path. - public static string AddRelative(this string path, string relativePart) + foreach (var directory in Directory.GetDirectories(sourcePath)) { - return (path + relativePart).ToFullPath(); + await CopyDirectoryAsync(directory, Path.Combine(destPath, Path.GetFileName(directory)), cancellationToken); } + } - /// - /// Executes a process with specified arguments and output handlers. - /// - /// The process file name. - /// Arguments for the process. - /// Action to handle standard output. - /// Action to handle standard error. - /// Optional action to modify process start info. - /// TimeSpan to wait for process exit. - /// Standard input to write to the process. - /// Optional process priority. - /// The process exit code. - public static int Execute(string fileName, string arg, Action output, Action error, Action infoHandler = null, TimeSpan waitForExit = default, string stdInput = null, ProcessPriorityClass? priority = null) - { - var source = new CancellationTokenSource(); - - if (waitForExit != default) - source.CancelAfter(waitForExit); + /// + /// Copies a file to the specified destination and makes the copy writable. + /// + /// The source file path. + /// The destination directory path. + /// The destination file path. + public static string CopyAndMakeWritable(string fileName, string destPath) + { + var destFile = Path.Combine(destPath, Path.GetFileName(fileName)); - return AsyncContext.Run(() => ExecuteAsync(fileName, arg, output, error, infoHandler, stdInput, priority, source.Token)); - } + File.Copy(fileName, destFile, true); + new FileInfo(destFile).IsReadOnly = false; - /// - /// Asynchronously executes a process with specified arguments and output handlers. - /// - /// The file name to execute. - /// Arguments for the process. - /// Action to handle standard output. - /// Action to handle standard error. - /// Optional action to modify process start info. - /// Standard input to send to the process. - /// Optional process priority. - /// A cancellation token. - /// A task that represents the asynchronous operation, containing the process exit code. - public static async Task ExecuteAsync(string fileName, string arg, Action output, Action error, Action infoHandler = null, string stdInput = null, ProcessPriorityClass? priority = null, CancellationToken cancellationToken = default) - { - if (output is null) - throw new ArgumentNullException(nameof(output)); + return destFile; + } - if (error is null) - throw new ArgumentNullException(nameof(error)); + /// + /// Converts a relative or partial path to a fully qualified path. + /// + /// The input path. + /// The absolute path. + public static string ToFullPath(this string path) + { + if (path is null) + throw new ArgumentNullException(nameof(path)); - var input = !stdInput.IsEmpty(); + return Path.GetFullPath(path); + } - var procInfo = new ProcessStartInfo(fileName, arg) - { - UseShellExecute = false, - RedirectStandardError = true, - RedirectStandardOutput = true, - RedirectStandardInput = input, - CreateNoWindow = true, - WindowStyle = ProcessWindowStyle.Hidden - }; + /// + /// Adds a relative segment to the current path and returns the fully qualified path. + /// + /// The base path. + /// The relative segment to add. + /// The combined full path. + public static string AddRelative(this string path, string relativePart) + { + return (path + relativePart).ToFullPath(); + } - infoHandler?.Invoke(procInfo); + /// + /// Executes a process with specified arguments and output handlers. + /// + /// The process file name. + /// Arguments for the process. + /// Action to handle standard output. + /// Action to handle standard error. + /// Optional action to modify process start info. + /// TimeSpan to wait for process exit. + /// Standard input to write to the process. + /// Optional process priority. + /// The process exit code. + public static int Execute(string fileName, string arg, Action output, Action error, Action infoHandler = null, TimeSpan waitForExit = default, string stdInput = null, ProcessPriorityClass? priority = null) + { + var source = new CancellationTokenSource(); - using var process = new Process - { - EnableRaisingEvents = true, - StartInfo = procInfo - }; + if (waitForExit != default) + source.CancelAfter(waitForExit); - process.Start(); + return AsyncContext.Run(() => ExecuteAsync(fileName, arg, output, error, infoHandler, stdInput, priority, source.Token)); + } - // Set process priority if provided. - // https://stackoverflow.com/a/1010377/8029915 - if (priority is not null) - process.PriorityClass = priority.Value; + /// + /// Asynchronously executes a process with specified arguments and output handlers. + /// + /// The file name to execute. + /// Arguments for the process. + /// Action to handle standard output. + /// Action to handle standard error. + /// Optional action to modify process start info. + /// Standard input to send to the process. + /// Optional process priority. + /// A cancellation token. + /// A task that represents the asynchronous operation, containing the process exit code. + public static async Task ExecuteAsync(string fileName, string arg, Action output, Action error, Action infoHandler = null, string stdInput = null, ProcessPriorityClass? priority = null, CancellationToken cancellationToken = default) + { + if (output is null) + throw new ArgumentNullException(nameof(output)); - var locker = new object(); + if (error is null) + throw new ArgumentNullException(nameof(error)); - if (input) - { - process.StandardInput.WriteLine(stdInput); - process.StandardInput.Close(); - } + var input = !stdInput.IsEmpty(); - async Task ReadProcessOutput(TextReader reader, Action action) - { - do - { - var str = await reader.ReadLineAsync().WithCancellation(cancellationToken); - if (str is null) - break; + var procInfo = new ProcessStartInfo(fileName, arg) + { + UseShellExecute = false, + RedirectStandardError = true, + RedirectStandardOutput = true, + RedirectStandardInput = input, + CreateNoWindow = true, + WindowStyle = ProcessWindowStyle.Hidden + }; - if (!str.IsEmptyOrWhiteSpace()) - { - lock (locker) - action(str); - } + infoHandler?.Invoke(procInfo); - cancellationToken.ThrowIfCancellationRequested(); - } - while (true); - } + using var process = new Process + { + EnableRaisingEvents = true, + StartInfo = procInfo + }; - var task1 = ReadProcessOutput(process.StandardOutput, output); - var task2 = ReadProcessOutput(process.StandardError, error); + process.Start(); - await task1; - await task2; + // Set process priority if provided. + // https://stackoverflow.com/a/1010377/8029915 + if (priority is not null) + process.PriorityClass = priority.Value; - await process.WaitForExitAsync(cancellationToken); + var locker = new object(); - return process.ExitCode; + if (input) + { + process.StandardInput.WriteLine(stdInput); + process.StandardInput.Close(); } - /// - /// Creates the directory for the specified file if it does not already exist. - /// - /// The full path to the file. - /// True if the directory was created; otherwise, false. - public static bool CreateDirIfNotExists(this string fullPath) + async Task ReadProcessOutput(TextReader reader, Action action) { - var directory = Path.GetDirectoryName(fullPath); + do + { + var str = await reader.ReadLineAsync().WithCancellation(cancellationToken); + if (str is null) + break; - if (directory.IsEmpty() || Directory.Exists(directory)) - return false; + if (!str.IsEmptyOrWhiteSpace()) + { + lock (locker) + action(str); + } - Directory.CreateDirectory(directory); - return true; + cancellationToken.ThrowIfCancellationRequested(); + } + while (true); } - private static readonly string[] _suf = ["B", "KB", "MB", "GB", "TB", "PB", "EB"]; //Longs run out around EB + var task1 = ReadProcessOutput(process.StandardOutput, output); + var task2 = ReadProcessOutput(process.StandardError, error); - /// - /// Converts a byte count to a human-readable file size string. - /// - /// The number of bytes. - /// A formatted string representing the file size. - public static string ToHumanReadableFileSize(this long byteCount) - { - int place; - int num; + await task1; + await task2; - if (byteCount == 0) - { - num = 0; - place = 0; - } - else - { - var bytes = byteCount.Abs(); - place = (int)Math.Log(bytes, FileSizes.KB).Floor(); - num = (int)(Math.Sign(byteCount) * Math.Round(bytes / Math.Pow(FileSizes.KB, place), 1)); - } + await process.WaitForExitAsync(cancellationToken); - return num + " " + _suf[place]; - } + return process.ExitCode; + } - /// - /// Safely deletes a directory. - /// - /// The directory path. - public static void SafeDeleteDir(this string path) - { - if (!Directory.Exists(path)) - return; + /// + /// Creates the directory for the specified file if it does not already exist. + /// + /// The full path to the file. + /// True if the directory was created; otherwise, false. + public static bool CreateDirIfNotExists(this string fullPath) + { + var directory = Path.GetDirectoryName(fullPath); - Directory.Delete(path, true); - } + if (directory.IsEmpty() || Directory.Exists(directory)) + return false; - /// - /// Creates a temporary directory and returns its path. - /// - /// The path to the new temporary directory. - public static string CreateTempDir() - { - var path = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString().Remove("-")); + Directory.CreateDirectory(directory); + return true; + } - if (!Directory.Exists(path)) - Directory.CreateDirectory(path); + private static readonly string[] _suf = ["B", "KB", "MB", "GB", "TB", "PB", "EB"]; //Longs run out around EB - return path; - } + /// + /// Converts a byte count to a human-readable file size string. + /// + /// The number of bytes. + /// A formatted string representing the file size. + public static string ToHumanReadableFileSize(this long byteCount) + { + int place; + int num; - /// - /// Checks if the specified installation directory exists and contains files or subdirectories. - /// - /// The installation directory path. - /// True if the installation is valid; otherwise, false. - public static bool CheckInstallation(string path) + if (byteCount == 0) { - if (path.IsEmpty()) - return false; + num = 0; + place = 0; + } + else + { + var bytes = byteCount.Abs(); + place = (int)Math.Log(bytes, FileSizes.KB).Floor(); + num = (int)(Math.Sign(byteCount) * Math.Round(bytes / Math.Pow(FileSizes.KB, place), 1)); + } - if (!Directory.Exists(path)) - return false; + return num + " " + _suf[place]; + } - var files = Directory.GetFiles(path); - var directories = Directory.GetDirectories(path); - return files.Any() || directories.Any(); - } + /// + /// Safely deletes a directory. + /// + /// The directory path. + public static void SafeDeleteDir(this string path) + { + if (!Directory.Exists(path)) + return; - /// - /// Gets the relative path from a folder to a file. - /// - /// The full file path. - /// The base folder. - /// The relative file path. - public static string GetRelativePath(this string fileFull, string folder) - { - var pathUri = new Uri(fileFull); + Directory.Delete(path, true); + } - if (!folder.EndsWith(Path.DirectorySeparatorChar.ToString())) - folder += Path.DirectorySeparatorChar; + /// + /// Creates a temporary directory and returns its path. + /// + /// The path to the new temporary directory. + public static string CreateTempDir() + { + var path = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString().Remove("-")); - var folderUri = new Uri(folder); - return folderUri.MakeRelativeUri(pathUri).ToString().Replace('/', Path.DirectorySeparatorChar).DataUnEscape(); - } + if (!Directory.Exists(path)) + Directory.CreateDirectory(path); - /// - /// Gets the available free space on the specified drive. - /// - /// The drive name (e.g., "C:"). - /// The amount of free space in bytes. - public static long GetDiskFreeSpace(string driveName) - { - return new DriveInfo(driveName).TotalFreeSpace; - } + return path; + } - /// - /// Creates a file with the specified content. - /// - /// The root path. - /// The relative path to the file. - /// The file name. - /// The content as a byte array. - public static void CreateFile(string rootPath, string relativePath, string fileName, byte[] content) - { - if (relativePath.IsEmpty()) - { - File.WriteAllBytes(Path.Combine(rootPath, fileName), content); - } - else - { - var fullPath = Path.Combine(rootPath, relativePath, fileName); - var fileInfo = new FileInfo(fullPath); - fileInfo.Directory.Create(); - File.WriteAllBytes(fullPath, content); - } - } + /// + /// Checks if the specified installation directory exists and contains files or subdirectories. + /// + /// The installation directory path. + /// True if the installation is valid; otherwise, false. + public static bool CheckInstallation(string path) + { + if (path.IsEmpty()) + return false; - // https://stackoverflow.com/a/2811746/8029915 + if (!Directory.Exists(path)) + return false; - /// - /// Recursively deletes empty directories starting from the specified directory. - /// - /// The root directory to check and delete if empty. - public static void DeleteEmptyDirs(string dir) - { - if (dir.IsEmpty()) - throw new ArgumentNullException(nameof(dir)); + var files = Directory.GetFiles(path); + var directories = Directory.GetDirectories(path); + return files.Any() || directories.Any(); + } - try - { - foreach (var d in Directory.EnumerateDirectories(dir)) - { - DeleteEmptyDirs(d); - } + /// + /// Gets the relative path from a folder to a file. + /// + /// The full file path. + /// The base folder. + /// The relative file path. + public static string GetRelativePath(this string fileFull, string folder) + { + var pathUri = new Uri(fileFull); - var entries = Directory.EnumerateFileSystemEntries(dir); + if (!folder.EndsWith(Path.DirectorySeparatorChar.ToString())) + folder += Path.DirectorySeparatorChar; - if (!entries.Any()) - { - try - { - Directory.Delete(dir); - } - catch (UnauthorizedAccessException) { } - catch (DirectoryNotFoundException) { } - } - } - catch (UnauthorizedAccessException) { } - } + var folderUri = new Uri(folder); + return folderUri.MakeRelativeUri(pathUri).ToString().Replace('/', Path.DirectorySeparatorChar).DataUnEscape(); + } - /// - /// The %Documents% variable. - /// - public const string DocsVar = "%Documents%"; - - /// - /// Replaces the %Documents% variable in the path with the actual Documents folder path. - /// - /// The path containing the %Documents% variable. - /// The fully qualified path with the Documents folder. - public static string ToFullPathIfNeed(this string path) - { - if (path is null) - throw new ArgumentNullException(nameof(path)); + /// + /// Gets the available free space on the specified drive. + /// + /// The drive name (e.g., "C:"). + /// The amount of free space in bytes. + public static long GetDiskFreeSpace(string driveName) + { + return new DriveInfo(driveName).TotalFreeSpace; + } - return path.ReplaceIgnoreCase(DocsVar, Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)); + /// + /// Creates a file with the specified content. + /// + /// The root path. + /// The relative path to the file. + /// The file name. + /// The content as a byte array. + public static void CreateFile(string rootPath, string relativePath, string fileName, byte[] content) + { + if (relativePath.IsEmpty()) + { + File.WriteAllBytes(Path.Combine(rootPath, fileName), content); + } + else + { + var fullPath = Path.Combine(rootPath, relativePath, fileName); + var fileInfo = new FileInfo(fullPath); + fileInfo.Directory.Create(); + File.WriteAllBytes(fullPath, content); } + } - /// - /// Deletes a directory in a blocking manner. - /// - /// The directory to delete. - /// Indicates whether to delete subdirectories recursively. - /// Number of iterations to attempt deletion. - /// Sleep duration between attempts in milliseconds. - /// True if the directory still exists after deletion attempts; otherwise, false. - public static bool BlockDeleteDir(string dir, bool isRecursive = false, int iterCount = 1000, int sleep = 0) + // https://stackoverflow.com/a/2811746/8029915 + + /// + /// Recursively deletes empty directories starting from the specified directory. + /// + /// The root directory to check and delete if empty. + public static void DeleteEmptyDirs(string dir) + { + if (dir.IsEmpty()) + throw new ArgumentNullException(nameof(dir)); + + try { - if (isRecursive) + foreach (var d in Directory.EnumerateDirectories(dir)) { - // https://stackoverflow.com/a/329502/8029915 - // Delete files and directories recursively. - var files = Directory.GetFiles(dir); - var dirs = Directory.GetDirectories(dir); + DeleteEmptyDirs(d); + } - foreach (var file in files) - { - File.SetAttributes(file, FileAttributes.Normal); - File.Delete(file); - } + var entries = Directory.EnumerateFileSystemEntries(dir); - foreach (var sub in dirs) + if (!entries.Any()) + { + try { - BlockDeleteDir(sub, true, iterCount, sleep); + Directory.Delete(dir); } + catch (UnauthorizedAccessException) { } + catch (DirectoryNotFoundException) { } } + } + catch (UnauthorizedAccessException) { } + } - // https://stackoverflow.com/a/1703799/8029915 - // Attempt deletion. + /// + /// The %Documents% variable. + /// + public const string DocsVar = "%Documents%"; - try - { - Directory.Delete(dir, false); - } - catch (IOException) + /// + /// Replaces the %Documents% variable in the path with the actual Documents folder path. + /// + /// The path containing the %Documents% variable. + /// The fully qualified path with the Documents folder. + public static string ToFullPathIfNeed(this string path) + { + if (path is null) + throw new ArgumentNullException(nameof(path)); + + return path.ReplaceIgnoreCase(DocsVar, Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)); + } + + /// + /// Deletes a directory in a blocking manner. + /// + /// The directory to delete. + /// Indicates whether to delete subdirectories recursively. + /// Number of iterations to attempt deletion. + /// Sleep duration between attempts in milliseconds. + /// True if the directory still exists after deletion attempts; otherwise, false. + public static bool BlockDeleteDir(string dir, bool isRecursive = false, int iterCount = 1000, int sleep = 0) + { + if (isRecursive) + { + // https://stackoverflow.com/a/329502/8029915 + // Delete files and directories recursively. + var files = Directory.GetFiles(dir); + var dirs = Directory.GetDirectories(dir); + + foreach (var file in files) { - Directory.Delete(dir, false); + File.SetAttributes(file, FileAttributes.Normal); + File.Delete(file); } - catch (UnauthorizedAccessException) + + foreach (var sub in dirs) { - Directory.Delete(dir, false); + BlockDeleteDir(sub, true, iterCount, sleep); } + } - var limit = iterCount; - - while (Directory.Exists(dir) && limit-- > 0) - Thread.Sleep(sleep); + // https://stackoverflow.com/a/1703799/8029915 + // Attempt deletion. - return Directory.Exists(dir); + try + { + Directory.Delete(dir, false); + } + catch (IOException) + { + Directory.Delete(dir, false); } + catch (UnauthorizedAccessException) + { + Directory.Delete(dir, false); + } + + var limit = iterCount; + + while (Directory.Exists(dir) && limit-- > 0) + Thread.Sleep(sleep); - /// - /// Opens the specified URL or file path using the default system launcher. - /// - /// The URL or file path to open. - /// Determines if an exception should be raised if opening fails. - /// True if the operation is successful; otherwise, false. - public static bool OpenLink(this string url, bool raiseError) + return Directory.Exists(dir); + } + + /// + /// Opens the specified URL or file path using the default system launcher. + /// + /// The URL or file path to open. + /// Determines if an exception should be raised if opening fails. + /// True if the operation is successful; otherwise, false. + public static bool OpenLink(this string url, bool raiseError) + { + if (url.IsEmpty()) + throw new ArgumentNullException(nameof(url)); + + // https://stackoverflow.com/a/21836079 + + try { - if (url.IsEmpty()) - throw new ArgumentNullException(nameof(url)); + // https://github.com/dotnet/wpf/issues/2566 - // https://stackoverflow.com/a/21836079 + var procInfo = new ProcessStartInfo(url) + { + UseShellExecute = true, + }; + Process.Start(procInfo); + return true; + } + catch (Win32Exception) + { try { - // https://github.com/dotnet/wpf/issues/2566 - - var procInfo = new ProcessStartInfo(url) - { - UseShellExecute = true, - }; - - Process.Start(procInfo); + var launcher = url.StartsWithIgnoreCase("http") ? "IExplore.exe" : "explorer.exe"; + Process.Start(launcher, url); return true; } - catch (Win32Exception) + catch { - try - { - var launcher = url.StartsWithIgnoreCase("http") ? "IExplore.exe" : "explorer.exe"; - Process.Start(launcher, url); - return true; - } - catch - { - if (raiseError) - throw; + if (raiseError) + throw; - return false; - } + return false; } } + } - /// - /// Retrieves the directories within the specified path matching the search pattern. - /// - /// The root directory to search. - /// The search pattern. - /// Search option to determine whether to search subdirectories. - /// An enumerable of matching directory paths. - public static IEnumerable GetDirectories(string path, - string searchPattern = "*", - SearchOption searchOption = SearchOption.TopDirectoryOnly) - { - return !Directory.Exists(path) - ? [] - : Directory.EnumerateDirectories(path, searchPattern, searchOption); - } + /// + /// Retrieves the directories within the specified path matching the search pattern. + /// + /// The root directory to search. + /// The search pattern. + /// Search option to determine whether to search subdirectories. + /// An enumerable of matching directory paths. + public static IEnumerable GetDirectories(string path, + string searchPattern = "*", + SearchOption searchOption = SearchOption.TopDirectoryOnly) + { + return !Directory.Exists(path) + ? [] + : Directory.EnumerateDirectories(path, searchPattern, searchOption); + } - /// - /// Gets the timestamp of the specified assembly. - /// - /// The assembly. - /// The timestamp when the assembly was built. - public static DateTime GetTimestamp(this Assembly assembly) - { - if (assembly is null) - throw new ArgumentNullException(nameof(assembly)); + /// + /// Gets the timestamp of the specified assembly. + /// + /// The assembly. + /// The timestamp when the assembly was built. + public static DateTime GetTimestamp(this Assembly assembly) + { + if (assembly is null) + throw new ArgumentNullException(nameof(assembly)); - return GetTimestamp(assembly.Location); - } + return GetTimestamp(assembly.Location); + } - /// - /// Gets the timestamp of the specified file. - /// - /// The file path. - /// The timestamp representing when the file was built. - public static DateTime GetTimestamp(string filePath) - { - var b = new byte[2048]; + /// + /// Gets the timestamp of the specified file. + /// + /// The file path. + /// The timestamp representing when the file was built. + public static DateTime GetTimestamp(string filePath) + { + var b = new byte[2048]; - using (var s = new FileStream(filePath, FileMode.Open, FileAccess.Read)) - s.Read(b, 0, b.Length); + using (var s = new FileStream(filePath, FileMode.Open, FileAccess.Read)) + s.Read(b, 0, b.Length); - const int peHeaderOffset = 60; - const int linkerTimestampOffset = 8; - var i = BitConverter.ToInt32(b, peHeaderOffset); - var secondsSince1970 = (long)BitConverter.ToInt32(b, i + linkerTimestampOffset); + const int peHeaderOffset = 60; + const int linkerTimestampOffset = 8; + var i = BitConverter.ToInt32(b, peHeaderOffset); + var secondsSince1970 = (long)BitConverter.ToInt32(b, i + linkerTimestampOffset); - return secondsSince1970.FromUnix().ToLocalTime(); - } + return secondsSince1970.FromUnix().ToLocalTime(); + } - /// - /// Determines whether the specified path represents a directory. - /// - /// The file or directory path. - /// True if the path is a directory; otherwise, false. - public static bool IsDirectory(this string path) => File.GetAttributes(path).HasFlag(FileAttributes.Directory); - - /// - /// Writes the specified bytes to a stream. - /// - /// The output stream. - /// The byte array. - /// The number of bytes to write. - /// The position in the array to start writing from. - public static void WriteBytes(this Stream stream, byte[] bytes, int len, int pos = 0) - { - stream.Write(bytes, pos, len); - } + /// + /// Determines whether the specified path represents a directory. + /// + /// The file or directory path. + /// True if the path is a directory; otherwise, false. + public static bool IsDirectory(this string path) => File.GetAttributes(path).HasFlag(FileAttributes.Directory); - /// - /// Reads a specified number of bytes from a stream. - /// - /// The input stream. - /// The buffer to fill. - /// The number of bytes to read. - /// The position in the buffer to start filling. - /// The buffer containing the read bytes. - public static byte[] ReadBytes(this Stream stream, byte[] buffer, int len, int pos = 0) - { - var left = len; + /// + /// Writes the specified bytes to a stream. + /// + /// The output stream. + /// The byte array. + /// The number of bytes to write. + /// The position in the array to start writing from. + public static void WriteBytes(this Stream stream, byte[] bytes, int len, int pos = 0) + { + stream.Write(bytes, pos, len); + } - while (left > 0) - { - var read = stream.Read(buffer, pos + (len - left), left); + /// + /// Reads a specified number of bytes from a stream. + /// + /// The input stream. + /// The buffer to fill. + /// The number of bytes to read. + /// The position in the buffer to start filling. + /// The buffer containing the read bytes. + public static byte[] ReadBytes(this Stream stream, byte[] buffer, int len, int pos = 0) + { + var left = len; - if (read <= 0) - throw new IOException($"Stream returned '{read}' bytes."); + while (left > 0) + { + var read = stream.Read(buffer, pos + (len - left), left); - left -= read; - } + if (read <= 0) + throw new IOException($"Stream returned '{read}' bytes."); - return buffer; + left -= read; } - /// - /// Reads a single byte from a stream. - /// - /// The input stream. - /// The buffer used for reading. - /// The byte read from the stream. - public static byte ReadByteEx(this Stream stream, byte[] buffer) - { - return stream.ReadBytes(buffer, 1)[0]; - } + return buffer; + } - /// - /// Writes a single byte to a stream. - /// - /// The output stream. - /// The buffer used for writing. - /// The byte value to write. - public static void WriteByteEx(this Stream stream, byte[] buffer, byte value) - { - buffer[0] = value; - stream.Write(buffer, 0, 1); - } + /// + /// Reads a single byte from a stream. + /// + /// The input stream. + /// The buffer used for reading. + /// The byte read from the stream. + public static byte ReadByteEx(this Stream stream, byte[] buffer) + { + return stream.ReadBytes(buffer, 1)[0]; + } - /// - /// Writes a short value to a stream with specified endianness. - /// - /// The output stream. - /// The buffer used for writing. - /// The short value to write. - /// Indicates if the value is in little-endian format. - public static unsafe void WriteShort(this Stream stream, byte[] buffer, short value, bool isLittleEndian) - { - fixed (byte* b = buffer) - *((short*)b) = value; + /// + /// Writes a single byte to a stream. + /// + /// The output stream. + /// The buffer used for writing. + /// The byte value to write. + public static void WriteByteEx(this Stream stream, byte[] buffer, byte value) + { + buffer[0] = value; + stream.Write(buffer, 0, 1); + } - stream.WriteBytes(buffer.ChangeOrder(2, isLittleEndian), 2); - } + /// + /// Writes a short value to a stream with specified endianness. + /// + /// The output stream. + /// The buffer used for writing. + /// The short value to write. + /// Indicates if the value is in little-endian format. + public static unsafe void WriteShort(this Stream stream, byte[] buffer, short value, bool isLittleEndian) + { + fixed (byte* b = buffer) + *((short*)b) = value; - /// - /// Reads a short value from a stream with specified endianness. - /// - /// The input stream. - /// The buffer used for reading. - /// Indicates if the value is in little-endian format. - /// The short value read. - public static short ReadShort(this Stream stream, byte[] buffer, bool isLittleEndian) - { - return BitConverter.ToInt16(stream.ReadBytes(buffer, 2).ChangeOrder(2, isLittleEndian), 0); - } + stream.WriteBytes(buffer.ChangeOrder(2, isLittleEndian), 2); + } - /// - /// Writes an unsigned short value to a stream with specified endianness. - /// - /// The output stream. - /// The buffer used for writing. - /// The unsigned short value to write. - /// Indicates if the value is in little-endian format. - [CLSCompliant(false)] - public static unsafe void WriteUShort(this Stream stream, byte[] buffer, ushort value, bool isLittleEndian) - { - fixed (byte* b = buffer) - *((ushort*)b) = value; + /// + /// Reads a short value from a stream with specified endianness. + /// + /// The input stream. + /// The buffer used for reading. + /// Indicates if the value is in little-endian format. + /// The short value read. + public static short ReadShort(this Stream stream, byte[] buffer, bool isLittleEndian) + { + return BitConverter.ToInt16(stream.ReadBytes(buffer, 2).ChangeOrder(2, isLittleEndian), 0); + } - stream.WriteBytes(buffer.ChangeOrder(2, isLittleEndian), 2); - } + /// + /// Writes an unsigned short value to a stream with specified endianness. + /// + /// The output stream. + /// The buffer used for writing. + /// The unsigned short value to write. + /// Indicates if the value is in little-endian format. + [CLSCompliant(false)] + public static unsafe void WriteUShort(this Stream stream, byte[] buffer, ushort value, bool isLittleEndian) + { + fixed (byte* b = buffer) + *((ushort*)b) = value; - /// - /// Reads an unsigned short value from a stream with specified endianness. - /// - /// The input stream. - /// The buffer used for reading. - /// Indicates if the value is in little-endian format. - /// The unsigned short value read. - [CLSCompliant(false)] - public static ushort ReadUShort(this Stream stream, byte[] buffer, bool isLittleEndian) - { - return BitConverter.ToUInt16(stream.ReadBytes(buffer, 2).ChangeOrder(2, isLittleEndian), 0); - } + stream.WriteBytes(buffer.ChangeOrder(2, isLittleEndian), 2); + } - /// - /// Writes an integer value to a stream with specified endianness. - /// - /// The output stream. - /// The buffer used for writing. - /// The integer value to write. - /// Indicates if the value is in little-endian format. - public static unsafe void WriteInt(this Stream stream, byte[] buffer, int value, bool isLittleEndian) - { - fixed (byte* b = buffer) - *((int*)b) = value; + /// + /// Reads an unsigned short value from a stream with specified endianness. + /// + /// The input stream. + /// The buffer used for reading. + /// Indicates if the value is in little-endian format. + /// The unsigned short value read. + [CLSCompliant(false)] + public static ushort ReadUShort(this Stream stream, byte[] buffer, bool isLittleEndian) + { + return BitConverter.ToUInt16(stream.ReadBytes(buffer, 2).ChangeOrder(2, isLittleEndian), 0); + } - stream.WriteBytes(buffer.ChangeOrder(4, isLittleEndian), 4); - } + /// + /// Writes an integer value to a stream with specified endianness. + /// + /// The output stream. + /// The buffer used for writing. + /// The integer value to write. + /// Indicates if the value is in little-endian format. + public static unsafe void WriteInt(this Stream stream, byte[] buffer, int value, bool isLittleEndian) + { + fixed (byte* b = buffer) + *((int*)b) = value; - /// - /// Reads an integer value from a stream with specified endianness. - /// - /// The input stream. - /// The buffer used for reading. - /// Indicates if the value is in little-endian format. - /// The integer value read. - public static int ReadInt(this Stream stream, byte[] buffer, bool isLittleEndian) - { - return BitConverter.ToInt32(stream.ReadBytes(buffer, 4).ChangeOrder(4, isLittleEndian), 0); - } + stream.WriteBytes(buffer.ChangeOrder(4, isLittleEndian), 4); + } - /// - /// Writes an unsigned integer value to a stream with specified endianness. - /// - /// The output stream. - /// The buffer used for writing. - /// The unsigned integer value to write. - /// Indicates if the value is in little-endian format. - [CLSCompliant(false)] - public static unsafe void WriteUInt(this Stream stream, byte[] buffer, uint value, bool isLittleEndian) - { - fixed (byte* b = buffer) - *((uint*)b) = value; + /// + /// Reads an integer value from a stream with specified endianness. + /// + /// The input stream. + /// The buffer used for reading. + /// Indicates if the value is in little-endian format. + /// The integer value read. + public static int ReadInt(this Stream stream, byte[] buffer, bool isLittleEndian) + { + return BitConverter.ToInt32(stream.ReadBytes(buffer, 4).ChangeOrder(4, isLittleEndian), 0); + } - stream.WriteBytes(buffer.ChangeOrder(4, isLittleEndian), 4); - } + /// + /// Writes an unsigned integer value to a stream with specified endianness. + /// + /// The output stream. + /// The buffer used for writing. + /// The unsigned integer value to write. + /// Indicates if the value is in little-endian format. + [CLSCompliant(false)] + public static unsafe void WriteUInt(this Stream stream, byte[] buffer, uint value, bool isLittleEndian) + { + fixed (byte* b = buffer) + *((uint*)b) = value; - /// - /// Reads an unsigned integer value from a stream with specified endianness. - /// - /// The input stream. - /// The buffer used for reading. - /// Indicates if the value is in little-endian format. - /// The unsigned integer value read. - [CLSCompliant(false)] - public static uint ReadUInt(this Stream stream, byte[] buffer, bool isLittleEndian) - { - return BitConverter.ToUInt32(stream.ReadBytes(buffer, 4).ChangeOrder(4, isLittleEndian), 0); - } + stream.WriteBytes(buffer.ChangeOrder(4, isLittleEndian), 4); + } - /// - /// Writes a long value to a stream with specified endianness. - /// - /// The output stream. - /// The buffer used for writing. - /// The long value to write. - /// Indicates if the value is in little-endian format. - /// The length in bytes to write (default is 8). - public static unsafe void WriteLong(this Stream stream, byte[] buffer, long value, bool isLittleEndian, int len = 8) - { - fixed (byte* b = buffer) - *((long*)b) = value; + /// + /// Reads an unsigned integer value from a stream with specified endianness. + /// + /// The input stream. + /// The buffer used for reading. + /// Indicates if the value is in little-endian format. + /// The unsigned integer value read. + [CLSCompliant(false)] + public static uint ReadUInt(this Stream stream, byte[] buffer, bool isLittleEndian) + { + return BitConverter.ToUInt32(stream.ReadBytes(buffer, 4).ChangeOrder(4, isLittleEndian), 0); + } - stream.WriteBytes(buffer.ChangeOrder(len, isLittleEndian), 8); - } + /// + /// Writes a long value to a stream with specified endianness. + /// + /// The output stream. + /// The buffer used for writing. + /// The long value to write. + /// Indicates if the value is in little-endian format. + /// The length in bytes to write (default is 8). + public static unsafe void WriteLong(this Stream stream, byte[] buffer, long value, bool isLittleEndian, int len = 8) + { + fixed (byte* b = buffer) + *((long*)b) = value; - /// - /// Reads a long value from a stream with specified endianness. - /// - /// The input stream. - /// The buffer used for reading. - /// Indicates if the value is in little-endian format. - /// The length in bytes to read (default is 8). - /// The long value read. - public static long ReadLong(this Stream stream, byte[] buffer, bool isLittleEndian, int len = 8) - { - return BitConverter.ToInt64(stream.ReadBytes(buffer, len).ChangeOrder(len, isLittleEndian), 0); - } + stream.WriteBytes(buffer.ChangeOrder(len, isLittleEndian), 8); + } - /// - /// Writes an unsigned long value to a stream with specified endianness. - /// - /// The output stream. - /// The buffer used for writing. - /// The unsigned long value to write. - /// Indicates if the value is in little-endian format. - /// The length in bytes to write (default is 8). - [CLSCompliant(false)] - public static unsafe void WriteULong(this Stream stream, byte[] buffer, ulong value, bool isLittleEndian, int len = 8) - { - fixed (byte* b = buffer) - *((ulong*)b) = value; + /// + /// Reads a long value from a stream with specified endianness. + /// + /// The input stream. + /// The buffer used for reading. + /// Indicates if the value is in little-endian format. + /// The length in bytes to read (default is 8). + /// The long value read. + public static long ReadLong(this Stream stream, byte[] buffer, bool isLittleEndian, int len = 8) + { + return BitConverter.ToInt64(stream.ReadBytes(buffer, len).ChangeOrder(len, isLittleEndian), 0); + } - stream.WriteBytes(buffer.ChangeOrder(len, isLittleEndian), 8); - } + /// + /// Writes an unsigned long value to a stream with specified endianness. + /// + /// The output stream. + /// The buffer used for writing. + /// The unsigned long value to write. + /// Indicates if the value is in little-endian format. + /// The length in bytes to write (default is 8). + [CLSCompliant(false)] + public static unsafe void WriteULong(this Stream stream, byte[] buffer, ulong value, bool isLittleEndian, int len = 8) + { + fixed (byte* b = buffer) + *((ulong*)b) = value; - /// - /// Reads an unsigned long value from a stream with specified endianness. - /// - /// The input stream. - /// The buffer used for reading. - /// Indicates if the value is in little-endian format. - /// The length in bytes to read (default is 8). - /// The unsigned long value read. - [CLSCompliant(false)] - public static ulong ReadULong(this Stream stream, byte[] buffer, bool isLittleEndian, int len = 8) - { - return BitConverter.ToUInt64(stream.ReadBytes(buffer, len).ChangeOrder(4, isLittleEndian), 0); - } + stream.WriteBytes(buffer.ChangeOrder(len, isLittleEndian), 8); + } + /// + /// Reads an unsigned long value from a stream with specified endianness. + /// + /// The input stream. + /// The buffer used for reading. + /// Indicates if the value is in little-endian format. + /// The length in bytes to read (default is 8). + /// The unsigned long value read. + [CLSCompliant(false)] + public static ulong ReadULong(this Stream stream, byte[] buffer, bool isLittleEndian, int len = 8) + { + return BitConverter.ToUInt64(stream.ReadBytes(buffer, len).ChangeOrder(4, isLittleEndian), 0); + } - /// - /// Copies a specified number of bytes synchronously from the source stream to the destination stream. - /// - /// The source stream to copy from. - /// The destination stream to copy to. - /// The number of bytes to copy. - public static void CopySync(this Stream source, Stream destination, int count) - { - if (source is null) - throw new ArgumentNullException(nameof(source)); - if (destination is null) - throw new ArgumentNullException(nameof(destination)); + /// + /// Copies a specified number of bytes synchronously from the source stream to the destination stream. + /// + /// The source stream to copy from. + /// The destination stream to copy to. + /// The number of bytes to copy. + public static void CopySync(this Stream source, Stream destination, int count) + { + if (source is null) + throw new ArgumentNullException(nameof(source)); - if (count < 0) - throw new ArgumentOutOfRangeException(nameof(count), count, "Invalid value."); + if (destination is null) + throw new ArgumentNullException(nameof(destination)); - if (count == 0) - return; + if (count < 0) + throw new ArgumentOutOfRangeException(nameof(count), count, "Invalid value."); - var buffer = new byte[count.Min(short.MaxValue)]; - int read; + if (count == 0) + return; - while (count > 0 && (read = source.Read(buffer, 0, buffer.Length.Min(count))) > 0) - { - destination.Write(buffer, 0, read); - count -= read; - } - } + var buffer = new byte[count.Min(short.MaxValue)]; + int read; - /// - /// Reads exactly the specified number of bytes from the stream into a byte array. - /// - /// The stream to read from. - /// The number of bytes to read. - /// A byte array containing the data read from the stream. - public static byte[] ReadBuffer(this Stream stream, int size) + while (count > 0 && (read = source.Read(buffer, 0, buffer.Length.Min(count))) > 0) { - if (stream is null) - throw new ArgumentNullException(nameof(stream)); + destination.Write(buffer, 0, read); + count -= read; + } + } + + /// + /// Reads exactly the specified number of bytes from the stream into a byte array. + /// + /// The stream to read from. + /// The number of bytes to read. + /// A byte array containing the data read from the stream. + public static byte[] ReadBuffer(this Stream stream, int size) + { + if (stream is null) + throw new ArgumentNullException(nameof(stream)); + + if (size < 0) + throw new ArgumentOutOfRangeException(nameof(size), $"Size has negative value '{size}'."); - if (size < 0) - throw new ArgumentOutOfRangeException(nameof(size), $"Size has negative value '{size}'."); + var buffer = new byte[size]; - var buffer = new byte[size]; + if (size == 1) + { + var b = stream.ReadByte(); + + if (b == -1) + throw new ArgumentException($"Insufficient stream size '{size}'.", nameof(stream)); - if (size == 1) + buffer[0] = (byte)b; + } + else + { + var offset = 0; + do { - var b = stream.ReadByte(); + var readBytes = stream.Read(buffer, offset, size - offset); - if (b == -1) + if (readBytes == 0) throw new ArgumentException($"Insufficient stream size '{size}'.", nameof(stream)); - buffer[0] = (byte)b; + offset += readBytes; } - else - { - var offset = 0; - do - { - var readBytes = stream.Read(buffer, offset, size - offset); + while (offset < size); + } - if (readBytes == 0) - throw new ArgumentException($"Insufficient stream size '{size}'.", nameof(stream)); + return buffer; + } - offset += readBytes; - } - while (offset < size); - } + /// + /// Enumerates the lines in the stream using the specified encoding. + /// + /// The stream to read lines from. + /// The encoding to use when reading the stream. Defaults to UTF8 if null. + /// Indicates whether to leave the stream open after reading. + /// An enumerable collection of strings, each representing a line from the stream. + public static IEnumerable EnumerateLines(this Stream stream, Encoding encoding = null, bool leaveOpen = true) + { + if (stream is null) + throw new ArgumentNullException(nameof(stream)); - return buffer; - } + using var sr = new StreamReader(stream, encoding ?? Encoding.UTF8, true, -1, leaveOpen); - /// - /// Enumerates the lines in the stream using the specified encoding. - /// - /// The stream to read lines from. - /// The encoding to use when reading the stream. Defaults to UTF8 if null. - /// Indicates whether to leave the stream open after reading. - /// An enumerable collection of strings, each representing a line from the stream. - public static IEnumerable EnumerateLines(this Stream stream, Encoding encoding = null, bool leaveOpen = true) - { - if (stream is null) - throw new ArgumentNullException(nameof(stream)); + while (!sr.EndOfStream) + yield return sr.ReadLine(); + } - using var sr = new StreamReader(stream, encoding ?? Encoding.UTF8, true, -1, leaveOpen); + /// + /// Writes an extended representation of the provided object to the stream, prefixing its length. + /// + /// The stream to write to. + /// The object to write. + public static void WriteEx(this Stream stream, object value) + { + if (stream is null) + throw new ArgumentNullException(nameof(stream)); - while (!sr.EndOfStream) - yield return sr.ReadLine(); - } + if (value is null) + throw new ArgumentNullException(nameof(value)); - /// - /// Writes an extended representation of the provided object to the stream, prefixing its length. - /// - /// The stream to write to. - /// The object to write. - public static void WriteEx(this Stream stream, object value) - { - if (stream is null) - throw new ArgumentNullException(nameof(stream)); + if (value is Stream s) + stream.WriteEx((int)s.Length); + else if (value is byte[] a1) + stream.WriteEx(a1.Length); + else if (value is string str) + stream.WriteEx(str.Length); - if (value is null) - throw new ArgumentNullException(nameof(value)); + stream.WriteRaw(value); + } - if (value is Stream s) - stream.WriteEx((int)s.Length); - else if (value is byte[] a1) - stream.WriteEx(a1.Length); - else if (value is string str) - stream.WriteEx(str.Length); + /// + /// Writes the raw byte representation of the provided object to the stream. + /// + /// The stream to write to. + /// The object to write. Its byte representation will be determined. + public static void WriteRaw(this Stream stream, object value) + { + stream.WriteRaw(value.To()); + } - stream.WriteRaw(value); - } + /// + /// Writes a raw byte array to the stream. + /// + /// The stream to write to. + /// The byte array to write. + public static void WriteRaw(this Stream stream, byte[] buffer) + { + if (stream is null) + throw new ArgumentNullException(nameof(stream)); - /// - /// Writes the raw byte representation of the provided object to the stream. - /// - /// The stream to write to. - /// The object to write. Its byte representation will be determined. - public static void WriteRaw(this Stream stream, object value) - { - stream.WriteRaw(value.To()); - } + if (buffer is null) + throw new ArgumentNullException(nameof(buffer)); - /// - /// Writes a raw byte array to the stream. - /// - /// The stream to write to. - /// The byte array to write. - public static void WriteRaw(this Stream stream, byte[] buffer) - { - if (stream is null) - throw new ArgumentNullException(nameof(stream)); + stream.Write(buffer, 0, buffer.Length); + } - if (buffer is null) - throw new ArgumentNullException(nameof(buffer)); + #region Read - stream.Write(buffer, 0, buffer.Length); - } + /// + /// Reads an object of type T from the stream. + /// + /// The type of object to read. + /// The stream to read from. + /// The object read from the stream. + public static T Read(this Stream stream) + { + return (T)stream.Read(typeof(T)); + } - #region Read + /// + /// Reads an object of the specified type from the stream. + /// + /// The stream to read from. + /// The type of object to read. + /// The object read from the stream. + public static object Read(this Stream stream, Type type) + { + int size; - /// - /// Reads an object of type T from the stream. - /// - /// The type of object to read. - /// The stream to read from. - /// The object read from the stream. - public static T Read(this Stream stream) - { - return (T)stream.Read(typeof(T)); - } + if (type == typeof(byte[]) || type == typeof(string) || type == typeof(Stream)) + size = stream.Read(); + else + size = type.SizeOf(); - /// - /// Reads an object of the specified type from the stream. - /// - /// The stream to read from. - /// The type of object to read. - /// The object read from the stream. - public static object Read(this Stream stream, Type type) - { - int size; + return stream.Read(type, size); + } - if (type == typeof(byte[]) || type == typeof(string) || type == typeof(Stream)) - size = stream.Read(); - else - size = type.SizeOf(); + /// + /// Reads an object of the specified type from the stream using the provided size. + /// + /// The stream to read from. + /// The type of object to read. + /// The size in bytes to read. + /// The object read from the stream. + public static object Read(this Stream stream, Type type, int size) + { + if (stream is null) + throw new ArgumentNullException(nameof(stream)); - return stream.Read(type, size); - } + if (type is null) + throw new ArgumentNullException(nameof(type)); - /// - /// Reads an object of the specified type from the stream using the provided size. - /// - /// The stream to read from. - /// The type of object to read. - /// The size in bytes to read. - /// The object read from the stream. - public static object Read(this Stream stream, Type type, int size) - { - if (stream is null) - throw new ArgumentNullException(nameof(stream)); + if (size < 0) + throw new ArgumentOutOfRangeException(nameof(size), $"Size has negative value '{size}'."); - if (type is null) - throw new ArgumentNullException(nameof(type)); + if (size == 0 && !(type == typeof(string) || type == typeof(byte[]) || type == typeof(Stream))) + throw new ArgumentOutOfRangeException(nameof(size), "Size has zero value."); - if (size < 0) - throw new ArgumentOutOfRangeException(nameof(size), $"Size has negative value '{size}'."); + if (type == typeof(string)) + size *= 2; - if (size == 0 && !(type == typeof(string) || type == typeof(byte[]) || type == typeof(Stream))) - throw new ArgumentOutOfRangeException(nameof(size), "Size has zero value."); + if (size > int.MaxValue / 10) + throw new ArgumentOutOfRangeException(nameof(size), $"Size has too big value {size}."); - if (type == typeof(string)) - size *= 2; + var buffer = size > 0 ? stream.ReadBuffer(size) : []; - if (size > int.MaxValue / 10) - throw new ArgumentOutOfRangeException(nameof(size), $"Size has too big value {size}."); + if (type == typeof(byte[])) + return buffer; + else + return buffer.To(type); + } - var buffer = size > 0 ? stream.ReadBuffer(size) : []; + #endregion - if (type == typeof(byte[])) - return buffer; - else - return buffer.To(type); - } + /// + /// Returns the size in bytes of an unmanaged type T. + /// + /// The unmanaged type. + /// The size in bytes of the specified type. + public static int SizeOf() + { + return SizeOf(typeof(T)); + } - #endregion + /// + /// Returns the size in bytes of the specified unmanaged type. + /// + /// The type whose size is to be computed. + /// The size in bytes of the specified type. + public static int SizeOf(this Type type) + { + if (type.IsDateTime()) + type = typeof(long); + else if (type == typeof(TimeSpan)) + type = typeof(long); + else if (type.IsEnum()) + type = type.GetEnumBaseType(); + else if (type == typeof(bool)) + type = typeof(byte); + else if (type == typeof(char)) + type = typeof(short); + + return Marshal.SizeOf(type); + } - /// - /// Returns the size in bytes of an unmanaged type T. - /// - /// The unmanaged type. - /// The size in bytes of the specified type. - public static int SizeOf() - { - return SizeOf(typeof(T)); - } + /// + /// Saves the content of the stream to a file specified by fileName. + /// + /// The stream whose contents to save. + /// The file path to save the stream's contents to. + /// The original stream. + public static Stream Save(this Stream stream, string fileName) + { + var pos = stream.CanSeek ? stream.Position : 0; - /// - /// Returns the size in bytes of the specified unmanaged type. - /// - /// The type whose size is to be computed. - /// The size in bytes of the specified type. - public static int SizeOf(this Type type) - { - if (type.IsDateTime()) - type = typeof(long); - else if (type == typeof(TimeSpan)) - type = typeof(long); - else if (type.IsEnum()) - type = type.GetEnumBaseType(); - else if (type == typeof(bool)) - type = typeof(byte); - else if (type == typeof(char)) - type = typeof(short); - - return Marshal.SizeOf(type); - } + using (var file = File.Create(fileName)) + stream.CopyTo(file); - /// - /// Saves the content of the stream to a file specified by fileName. - /// - /// The stream whose contents to save. - /// The file path to save the stream's contents to. - /// The original stream. - public static Stream Save(this Stream stream, string fileName) - { - var pos = stream.CanSeek ? stream.Position : 0; + if (stream.CanSeek) + stream.Position = pos; - using (var file = File.Create(fileName)) - stream.CopyTo(file); + return stream; + } - if (stream.CanSeek) - stream.Position = pos; + /// + /// Saves the byte array to a file specified by fileName. + /// + /// The byte array to save. + /// The file path to save the data to. + /// The original byte array. + public static byte[] Save(this byte[] data, string fileName) + { + data.To().Save(fileName); + return data; + } - return stream; - } + /// + /// Attempts to save the byte array to a file and handles any exceptions using the provided errorHandler. + /// + /// The byte array to save. + /// The file path to save the data to. + /// The action to handle exceptions. + /// True if the save operation was successful; otherwise, false. + public static bool TrySave(this byte[] data, string fileName, Action errorHandler) + { + if (errorHandler is null) + throw new ArgumentNullException(nameof(errorHandler)); - /// - /// Saves the byte array to a file specified by fileName. - /// - /// The byte array to save. - /// The file path to save the data to. - /// The original byte array. - public static byte[] Save(this byte[] data, string fileName) + try { data.To().Save(fileName); - return data; - } - - /// - /// Attempts to save the byte array to a file and handles any exceptions using the provided errorHandler. - /// - /// The byte array to save. - /// The file path to save the data to. - /// The action to handle exceptions. - /// True if the save operation was successful; otherwise, false. - public static bool TrySave(this byte[] data, string fileName, Action errorHandler) - { - if (errorHandler is null) - throw new ArgumentNullException(nameof(errorHandler)); - - try - { - data.To().Save(fileName); - return true; - } - catch (Exception e) - { - errorHandler(e); - return false; - } + return true; } - - /// - /// Truncates the underlying stream used by the StreamWriter by clearing its content. - /// - /// The StreamWriter whose stream is to be truncated. - public static void Truncate(this StreamWriter writer) + catch (Exception e) { - if (writer is null) - throw new ArgumentNullException(nameof(writer)); - - writer.Flush(); - writer.BaseStream.SetLength(0); + errorHandler(e); + return false; } + } - /// - /// Gets the actual buffer of the MemoryStream that contains the written data. - /// - /// The MemoryStream to retrieve the buffer from. - /// An ArraySegment containing the actual data from the MemoryStream. - public static ArraySegment GetActualBuffer(this MemoryStream stream) - { - if (stream is null) - throw new ArgumentNullException(nameof(stream)); + /// + /// Truncates the underlying stream used by the StreamWriter by clearing its content. + /// + /// The StreamWriter whose stream is to be truncated. + public static void Truncate(this StreamWriter writer) + { + if (writer is null) + throw new ArgumentNullException(nameof(writer)); - return new(stream.GetBuffer(), 0, (int)stream.Position); - } + writer.Flush(); + writer.BaseStream.SetLength(0); + } - /// - /// Checks whether the directory contains files or subdirectories that contain files. - /// - /// The directory path to check. - /// True if the directory contains any files; otherwise, false. - public static bool CheckDirContainFiles(string path) - { - return - Directory.Exists(path) && - (Directory.GetFiles(path).Any() || Directory.GetDirectories(path).Any(CheckDirContainFiles)); - } + /// + /// Gets the actual buffer of the MemoryStream that contains the written data. + /// + /// The MemoryStream to retrieve the buffer from. + /// An ArraySegment containing the actual data from the MemoryStream. + public static ArraySegment GetActualBuffer(this MemoryStream stream) + { + if (stream is null) + throw new ArgumentNullException(nameof(stream)); - /// - /// Checks whether the directory contains any files or subdirectories. - /// - /// The directory path to check. - /// True if the directory contains any entries; otherwise, false. - public static bool CheckDirContainsAnything(string path) - { - if (!Directory.Exists(path)) - return false; + return new(stream.GetBuffer(), 0, (int)stream.Position); + } - return Directory.EnumerateFileSystemEntries(path).Any(); - } + /// + /// Checks whether the directory contains files or subdirectories that contain files. + /// + /// The directory path to check. + /// True if the directory contains any files; otherwise, false. + public static bool CheckDirContainFiles(string path) + { + return + Directory.Exists(path) && + (Directory.GetFiles(path).Any() || Directory.GetDirectories(path).Any(CheckDirContainFiles)); + } - /// - /// Determines whether the file specified by the path is locked by another process. - /// - /// The path to the file to check. - /// True if the file is locked; otherwise, false. - public static bool IsFileLocked(string path) - { - var info = new FileInfo(path); + /// + /// Checks whether the directory contains any files or subdirectories. + /// + /// The directory path to check. + /// True if the directory contains any entries; otherwise, false. + public static bool CheckDirContainsAnything(string path) + { + if (!Directory.Exists(path)) + return false; - if (!info.Exists) - return false; + return Directory.EnumerateFileSystemEntries(path).Any(); + } - try - { - using var stream = info.Open(FileMode.Open, FileAccess.Read, FileShare.None); - } - catch (IOException) - { - return true; - } + /// + /// Determines whether the file specified by the path is locked by another process. + /// + /// The path to the file to check. + /// True if the file is locked; otherwise, false. + public static bool IsFileLocked(string path) + { + var info = new FileInfo(path); + if (!info.Exists) return false; - } - /// - /// Determines if the specified path refers to a directory. - /// - /// The path to check. - /// True if the path is a directory; otherwise, false. - public static bool IsPathIsDir(this string path) - => File.GetAttributes(path).HasFlag(FileAttributes.Directory); - - /// - /// Normalizes the provided file path for comparison purposes without converting to lowercase. - /// - /// The file path to normalize. - /// The normalized file path, or null if the input is empty or whitespace. - public static string NormalizePathNoLowercase(this string path) + try { - if (path.IsEmptyOrWhiteSpace()) - return null; - - path = Path.GetFullPath(path); - - return Path.GetFullPath(new Uri(path).LocalPath) - .TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) - .Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); + using var stream = info.Open(FileMode.Open, FileAccess.Read, FileShare.None); } - - /// - /// Normalizes the provided file path for comparison purposes and converts it to lowercase based on the specified culture. - /// - /// The file path to normalize. - /// The culture info to use for lowercasing. Defaults to InstalledUICulture if null. - /// The normalized and lowercased file path. - public static string NormalizePath(this string path, CultureInfo culture = null) + catch (IOException) { - return path.NormalizePathNoLowercase()?.ToLower(culture ?? CultureInfo.InstalledUICulture); + return true; } - /// - /// Compares two file paths for equality after normalization. - /// - /// The first file path to compare. - /// The second file path to compare. - /// True if both paths are equal; otherwise, false. - public static bool IsPathsEqual(string path1, string path2) => path1.NormalizePath() == path2.NormalizePath(); + return false; + } + + /// + /// Determines if the specified path refers to a directory. + /// + /// The path to check. + /// True if the path is a directory; otherwise, false. + public static bool IsPathIsDir(this string path) + => File.GetAttributes(path).HasFlag(FileAttributes.Directory); + + /// + /// Normalizes the provided file path for comparison purposes without converting to lowercase. + /// + /// The file path to normalize. + /// The normalized file path, or null if the input is empty or whitespace. + public static string NormalizePathNoLowercase(this string path) + { + if (path.IsEmptyOrWhiteSpace()) + return null; + + path = Path.GetFullPath(path); + return Path.GetFullPath(new Uri(path).LocalPath) + .TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + .Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); } + + /// + /// Normalizes the provided file path for comparison purposes and converts it to lowercase based on the specified culture. + /// + /// The file path to normalize. + /// The culture info to use for lowercasing. Defaults to InstalledUICulture if null. + /// The normalized and lowercased file path. + public static string NormalizePath(this string path, CultureInfo culture = null) + { + return path.NormalizePathNoLowercase()?.ToLower(culture ?? CultureInfo.InstalledUICulture); + } + + /// + /// Compares two file paths for equality after normalization. + /// + /// The first file path to compare. + /// The second file path to compare. + /// True if both paths are equal; otherwise, false. + public static bool IsPathsEqual(string path1, string path2) => path1.NormalizePath() == path2.NormalizePath(); + } \ No newline at end of file diff --git a/Common/IOperable.cs b/Common/IOperable.cs index bc839cb6..55f599fa 100644 --- a/Common/IOperable.cs +++ b/Common/IOperable.cs @@ -1,39 +1,38 @@ -namespace Ecng.Common -{ - using System; +namespace Ecng.Common; + +using System; +/// +/// Provides operations for performing arithmetic calculations on objects of type T. +/// +/// The type on which arithmetic operations are performed. +public interface IOperable : IComparable +{ /// - /// Provides operations for performing arithmetic calculations on objects of type T. + /// Adds the specified object to the current object. /// - /// The type on which arithmetic operations are performed. - public interface IOperable : IComparable - { - /// - /// Adds the specified object to the current object. - /// - /// The object to add. - /// The result of the addition operation. - T Add(T other); + /// The object to add. + /// The result of the addition operation. + T Add(T other); - /// - /// Subtracts the specified object from the current object. - /// - /// The object to subtract. - /// The result of the subtraction operation. - T Subtract(T other); + /// + /// Subtracts the specified object from the current object. + /// + /// The object to subtract. + /// The result of the subtraction operation. + T Subtract(T other); - /// - /// Multiplies the current object by the specified object. - /// - /// The object to multiply with. - /// The result of the multiplication operation. - T Multiply(T other); + /// + /// Multiplies the current object by the specified object. + /// + /// The object to multiply with. + /// The result of the multiplication operation. + T Multiply(T other); - /// - /// Divides the current object by the specified object. - /// - /// The object to divide by. - /// The result of the division operation. - T Divide(T other); - } + /// + /// Divides the current object by the specified object. + /// + /// The object to divide by. + /// The result of the division operation. + T Divide(T other); } \ No newline at end of file diff --git a/Common/IOperator.cs b/Common/IOperator.cs index b943b258..7895202b 100644 --- a/Common/IOperator.cs +++ b/Common/IOperator.cs @@ -1,833 +1,832 @@ -namespace Ecng.Common +namespace Ecng.Common; + +using System; +using System.Collections; +using System.Collections.Generic; + +/// +/// Provides basic arithmetic operations and comparison functionality. +/// +public interface IOperator : IComparer +{ + /// + /// Adds two objects. + /// + /// The first object. + /// The second object. + /// The result of addition. + object Add(object first, object second); + + /// + /// Subtracts the second object from the first. + /// + /// The first object. + /// The second object. + /// The result of subtraction. + object Subtract(object first, object second); + + /// + /// Multiplies two objects. + /// + /// The first object. + /// The second object. + /// The result of multiplication. + object Multiply(object first, object second); + + /// + /// Divides the first object by the second. + /// + /// The first object. + /// The second object. + /// The result of division. + object Divide(object first, object second); +} + +/// +/// Provides strongly-typed arithmetic operations and comparison functionality. +/// +/// The type to operate on. +public interface IOperator : IComparer, IOperator +{ + /// + /// Adds two values of type T. + /// + /// The first value. + /// The second value. + /// The result of addition. + T Add(T first, T second); + + /// + /// Subtracts the second value from the first. + /// + /// The first value. + /// The second value. + /// The result of subtraction. + T Subtract(T first, T second); + + /// + /// Multiplies two values of type T. + /// + /// The first value. + /// The second value. + /// The result of multiplication. + T Multiply(T first, T second); + + /// + /// Divides the first value by the second. + /// + /// The first value. + /// The second value. + /// The result of division. + T Divide(T first, T second); +} + +/// +/// Provides a base implementation for arithmetic operators. +/// +/// The type to operate on. +public abstract class BaseOperator : IOperator +{ + /// + /// Compares two values. + /// + /// The first value. + /// The second value. + /// A value indicating the relative order. + public abstract int Compare(T x, T y); + + /// + /// Compares two objects. + /// + /// The first object. + /// The second object. + /// A value indicating the relative order. + int IComparer.Compare(object x, object y) + { + return Compare((T)x, (T)y); + } + + /// + /// Adds two objects. + /// + /// The first object. + /// The second object. + /// The result of addition. + object IOperator.Add(object first, object second) + { + return Add((T)first, (T)second); + } + + /// + /// Subtracts the second object from the first. + /// + /// The first object. + /// The second object. + /// The result of subtraction. + object IOperator.Subtract(object first, object second) + { + return Subtract((T)first, (T)second); + } + + /// + /// Multiplies two objects. + /// + /// The first object. + /// The second object. + /// The result of multiplication. + object IOperator.Multiply(object first, object second) + { + return Multiply((T)first, (T)second); + } + + /// + /// Divides the first object by the second. + /// + /// The first object. + /// The second object. + /// The result of division. + object IOperator.Divide(object first, object second) + { + return Divide((T)first, (T)second); + } + + /// + /// Adds two values. + /// + /// The first value. + /// The second value. + /// The result of addition. + public abstract T Add(T first, T second); + + /// + /// Subtracts the second value from the first. + /// + /// The first value. + /// The second value. + /// The result of subtraction. + public abstract T Subtract(T first, T second); + + /// + /// Multiplies two values. + /// + /// The first value. + /// The second value. + /// The result of multiplication. + public abstract T Multiply(T first, T second); + + /// + /// Divides the first value by the second. + /// + /// The first value. + /// The second value. + /// The result of division. + public abstract T Divide(T first, T second); +} + +/// +/// Implements arithmetic operations for integers. +/// +public class IntOperator : BaseOperator +{ + /// + /// Adds two integers. + /// + public override int Add(int first, int second) + { + return first + second; + } + + /// + /// Subtracts the second integer from the first. + /// + public override int Subtract(int first, int second) + { + return first - second; + } + + /// + /// Multiplies two integers. + /// + public override int Multiply(int first, int second) + { + return first * second; + } + + /// + /// Divides the first integer by the second. + /// + public override int Divide(int first, int second) + { + return first / second; + } + + /// + /// Compares two integers. + /// + public override int Compare(int first, int second) + { + return first.CompareTo(second); + } +} + +/// +/// Implements arithmetic operations for unsigned integers. +/// +[CLSCompliant(false)] +public class UIntOperator : BaseOperator +{ + /// + /// Adds two unsigned integers. + /// + public override uint Add(uint first, uint second) + { + return first + second; + } + + /// + /// Subtracts the second unsigned integer from the first. + /// + public override uint Subtract(uint first, uint second) + { + return first - second; + } + + /// + /// Multiplies two unsigned integers. + /// + public override uint Multiply(uint first, uint second) + { + return first * second; + } + + /// + /// Divides the first unsigned integer by the second. + /// + public override uint Divide(uint first, uint second) + { + return first / second; + } + + /// + /// Compares two unsigned integers. + /// + public override int Compare(uint first, uint second) + { + return first.CompareTo(second); + } +} + +/// +/// Implements arithmetic operations for short integers. +/// +public class ShortOperator : BaseOperator +{ + /// + /// Adds two short integers. + /// + public override short Add(short first, short second) + { + return (short)(first + second); + } + + /// + /// Subtracts the second short integer from the first. + /// + public override short Subtract(short first, short second) + { + return (short)(first - second); + } + + /// + /// Multiplies two short integers. + /// + public override short Multiply(short first, short second) + { + return (short)(first * second); + } + + /// + /// Divides the first short integer by the second. + /// + public override short Divide(short first, short second) + { + return (short)(first / second); + } + + /// + /// Compares two short integers. + /// + public override int Compare(short first, short second) + { + return first.CompareTo(second); + } +} + +/// +/// Implements arithmetic operations for unsigned short integers. +/// +[CLSCompliant(false)] +public class UShortOperator : BaseOperator { - using System; - using System.Collections; - using System.Collections.Generic; - - /// - /// Provides basic arithmetic operations and comparison functionality. - /// - public interface IOperator : IComparer - { - /// - /// Adds two objects. - /// - /// The first object. - /// The second object. - /// The result of addition. - object Add(object first, object second); - - /// - /// Subtracts the second object from the first. - /// - /// The first object. - /// The second object. - /// The result of subtraction. - object Subtract(object first, object second); - - /// - /// Multiplies two objects. - /// - /// The first object. - /// The second object. - /// The result of multiplication. - object Multiply(object first, object second); - - /// - /// Divides the first object by the second. - /// - /// The first object. - /// The second object. - /// The result of division. - object Divide(object first, object second); - } - - /// - /// Provides strongly-typed arithmetic operations and comparison functionality. - /// - /// The type to operate on. - public interface IOperator : IComparer, IOperator - { - /// - /// Adds two values of type T. - /// - /// The first value. - /// The second value. - /// The result of addition. - T Add(T first, T second); - - /// - /// Subtracts the second value from the first. - /// - /// The first value. - /// The second value. - /// The result of subtraction. - T Subtract(T first, T second); - - /// - /// Multiplies two values of type T. - /// - /// The first value. - /// The second value. - /// The result of multiplication. - T Multiply(T first, T second); - - /// - /// Divides the first value by the second. - /// - /// The first value. - /// The second value. - /// The result of division. - T Divide(T first, T second); - } - - /// - /// Provides a base implementation for arithmetic operators. - /// - /// The type to operate on. - public abstract class BaseOperator : IOperator - { - /// - /// Compares two values. - /// - /// The first value. - /// The second value. - /// A value indicating the relative order. - public abstract int Compare(T x, T y); - - /// - /// Compares two objects. - /// - /// The first object. - /// The second object. - /// A value indicating the relative order. - int IComparer.Compare(object x, object y) - { - return Compare((T)x, (T)y); - } - - /// - /// Adds two objects. - /// - /// The first object. - /// The second object. - /// The result of addition. - object IOperator.Add(object first, object second) - { - return Add((T)first, (T)second); - } - - /// - /// Subtracts the second object from the first. - /// - /// The first object. - /// The second object. - /// The result of subtraction. - object IOperator.Subtract(object first, object second) - { - return Subtract((T)first, (T)second); - } - - /// - /// Multiplies two objects. - /// - /// The first object. - /// The second object. - /// The result of multiplication. - object IOperator.Multiply(object first, object second) - { - return Multiply((T)first, (T)second); - } - - /// - /// Divides the first object by the second. - /// - /// The first object. - /// The second object. - /// The result of division. - object IOperator.Divide(object first, object second) - { - return Divide((T)first, (T)second); - } - - /// - /// Adds two values. - /// - /// The first value. - /// The second value. - /// The result of addition. - public abstract T Add(T first, T second); - - /// - /// Subtracts the second value from the first. - /// - /// The first value. - /// The second value. - /// The result of subtraction. - public abstract T Subtract(T first, T second); - - /// - /// Multiplies two values. - /// - /// The first value. - /// The second value. - /// The result of multiplication. - public abstract T Multiply(T first, T second); - - /// - /// Divides the first value by the second. - /// - /// The first value. - /// The second value. - /// The result of division. - public abstract T Divide(T first, T second); - } - - /// - /// Implements arithmetic operations for integers. - /// - public class IntOperator : BaseOperator - { - /// - /// Adds two integers. - /// - public override int Add(int first, int second) - { - return first + second; - } - - /// - /// Subtracts the second integer from the first. - /// - public override int Subtract(int first, int second) - { - return first - second; - } - - /// - /// Multiplies two integers. - /// - public override int Multiply(int first, int second) - { - return first * second; - } - - /// - /// Divides the first integer by the second. - /// - public override int Divide(int first, int second) - { - return first / second; - } - - /// - /// Compares two integers. - /// - public override int Compare(int first, int second) - { - return first.CompareTo(second); - } - } - - /// - /// Implements arithmetic operations for unsigned integers. - /// - [CLSCompliant(false)] - public class UIntOperator : BaseOperator - { - /// - /// Adds two unsigned integers. - /// - public override uint Add(uint first, uint second) - { - return first + second; - } - - /// - /// Subtracts the second unsigned integer from the first. - /// - public override uint Subtract(uint first, uint second) - { - return first - second; - } - - /// - /// Multiplies two unsigned integers. - /// - public override uint Multiply(uint first, uint second) - { - return first * second; - } - - /// - /// Divides the first unsigned integer by the second. - /// - public override uint Divide(uint first, uint second) - { - return first / second; - } - - /// - /// Compares two unsigned integers. - /// - public override int Compare(uint first, uint second) - { - return first.CompareTo(second); - } - } - - /// - /// Implements arithmetic operations for short integers. - /// - public class ShortOperator : BaseOperator - { - /// - /// Adds two short integers. - /// - public override short Add(short first, short second) - { - return (short)(first + second); - } - - /// - /// Subtracts the second short integer from the first. - /// - public override short Subtract(short first, short second) - { - return (short)(first - second); - } - - /// - /// Multiplies two short integers. - /// - public override short Multiply(short first, short second) - { - return (short)(first * second); - } - - /// - /// Divides the first short integer by the second. - /// - public override short Divide(short first, short second) - { - return (short)(first / second); - } - - /// - /// Compares two short integers. - /// - public override int Compare(short first, short second) - { - return first.CompareTo(second); - } - } - - /// - /// Implements arithmetic operations for unsigned short integers. - /// - [CLSCompliant(false)] - public class UShortOperator : BaseOperator - { - /// - /// Adds two unsigned short integers. - /// - public override ushort Add(ushort first, ushort second) - { - return (ushort)(first + second); - } - - /// - /// Subtracts the second unsigned short integer from the first. - /// - public override ushort Subtract(ushort first, ushort second) - { - return (ushort)(first - second); - } - - /// - /// Multiplies two unsigned short integers. - /// - public override ushort Multiply(ushort first, ushort second) - { - return (ushort)(first * second); - } - - /// - /// Divides the first unsigned short integer by the second. - /// - public override ushort Divide(ushort first, ushort second) - { - return (ushort)(first / second); - } - - /// - /// Compares two unsigned short integers. - /// - public override int Compare(ushort first, ushort second) - { - return first.CompareTo(second); - } - } - - /// - /// Implements arithmetic operations for long integers. - /// - public class LongOperator : BaseOperator - { - /// - /// Adds two long integers. - /// - public override long Add(long first, long second) - { - return first + second; - } - - /// - /// Subtracts the second long integer from the first. - /// - public override long Subtract(long first, long second) - { - return first - second; - } - - /// - /// Multiplies two long integers. - /// - public override long Multiply(long first, long second) - { - return first * second; - } - - /// - /// Divides the first long integer by the second. - /// - public override long Divide(long first, long second) - { - return first / second; - } - - /// - /// Compares two long integers. - /// - public override int Compare(long first, long second) - { - return first.CompareTo(second); - } - } - - /// - /// Implements arithmetic operations for unsigned long integers. - /// - [CLSCompliant(false)] - public class ULongOperator : BaseOperator - { - /// - /// Adds two unsigned long integers. - /// - public override ulong Add(ulong first, ulong second) - { - return first + second; - } - - /// - /// Subtracts the second unsigned long integer from the first. - /// - public override ulong Subtract(ulong first, ulong second) - { - return first - second; - } - - /// - /// Multiplies two unsigned long integers. - /// - public override ulong Multiply(ulong first, ulong second) - { - return first * second; - } - - /// - /// Divides the first unsigned long integer by the second. - /// - public override ulong Divide(ulong first, ulong second) - { - return first / second; - } - - /// - /// Compares two unsigned long integers. - /// - public override int Compare(ulong first, ulong second) - { - return first.CompareTo(second); - } - } - - /// - /// Implements arithmetic operations for floating-point numbers. - /// - public class FloatOperator : BaseOperator - { - /// - /// Adds two float values. - /// - public override float Add(float first, float second) - { - return first + second; - } - - /// - /// Subtracts the second float value from the first. - /// - public override float Subtract(float first, float second) - { - return first - second; - } - - /// - /// Multiplies two float values. - /// - public override float Multiply(float first, float second) - { - return first * second; - } - - /// - /// Divides the first float value by the second. - /// - public override float Divide(float first, float second) - { - return first / second; - } - - /// - /// Compares two float values. - /// - public override int Compare(float first, float second) - { - return first.CompareTo(second); - } - } - - /// - /// Implements arithmetic operations for double-precision floating-point numbers. - /// - public class DoubleOperator : BaseOperator - { - /// - /// Adds two double values. - /// - public override double Add(double first, double second) - { - return first + second; - } - - /// - /// Subtracts the second double value from the first. - /// - public override double Subtract(double first, double second) - { - return first - second; - } - - /// - /// Multiplies two double values. - /// - public override double Multiply(double first, double second) - { - return first * second; - } - - /// - /// Divides the first double value by the second. - /// - public override double Divide(double first, double second) - { - return first / second; - } - - /// - /// Compares two double values. - /// - public override int Compare(double first, double second) - { - return first.CompareTo(second); - } - } - - /// - /// Implements arithmetic operations for byte values. - /// - public class ByteOperator : BaseOperator - { - /// - /// Adds two byte values. - /// - public override byte Add(byte first, byte second) - { - return (byte)(first + second); - } - - /// - /// Subtracts the second byte value from the first. - /// - public override byte Subtract(byte first, byte second) - { - return (byte)(first - second); - } - - /// - /// Multiplies two byte values. - /// - public override byte Multiply(byte first, byte second) - { - return (byte)(first * second); - } - - /// - /// Divides the first byte value by the second. - /// - public override byte Divide(byte first, byte second) - { - return (byte)(first / second); - } - - /// - /// Compares two byte values. - /// - public override int Compare(byte first, byte second) - { - return first.CompareTo(second); - } - } - - /// - /// Implements arithmetic operations for signed byte values. - /// - [CLSCompliant(false)] - public class SByteOperator : BaseOperator - { - /// - /// Adds two sbyte values. - /// - public override sbyte Add(sbyte first, sbyte second) - { - return (sbyte)(first + second); - } - - /// - /// Subtracts the second sbyte value from the first. - /// - public override sbyte Subtract(sbyte first, sbyte second) - { - return (sbyte)(first - second); - } - - /// - /// Multiplies two sbyte values. - /// - public override sbyte Multiply(sbyte first, sbyte second) - { - return (sbyte)(first * second); - } - - /// - /// Divides the first sbyte value by the second. - /// - public override sbyte Divide(sbyte first, sbyte second) - { - return (sbyte)(first / second); - } - - /// - /// Compares two sbyte values. - /// - public override int Compare(sbyte first, sbyte second) - { - return first.CompareTo(second); - } - } - - /// - /// Implements arithmetic operations for decimal numbers. - /// - public class DecimalOperator : BaseOperator - { - /// - /// Adds two decimals. - /// - public override decimal Add(decimal first, decimal second) - { - return first + second; - } - - /// - /// Subtracts the second decimal from the first. - /// - public override decimal Subtract(decimal first, decimal second) - { - return first - second; - } - - /// - /// Multiplies two decimals. - /// - public override decimal Multiply(decimal first, decimal second) - { - return first * second; - } - - /// - /// Divides the first decimal by the second. - /// - public override decimal Divide(decimal first, decimal second) - { - return first / second; - } - - /// - /// Compares two decimals. - /// - public override int Compare(decimal first, decimal second) - { - return first.CompareTo(second); - } - } - - /// - /// Implements arithmetic operations for TimeSpan values. - /// - public class TimeSpanOperator : BaseOperator - { - /// - /// Adds two TimeSpan values. - /// - public override TimeSpan Add(TimeSpan first, TimeSpan second) - { - return first + second; - } - - /// - /// Subtracts the second TimeSpan from the first. - /// - public override TimeSpan Subtract(TimeSpan first, TimeSpan second) - { - return first - second; - } - - /// - /// Multiplies two TimeSpan values by multiplying their ticks. - /// - public override TimeSpan Multiply(TimeSpan first, TimeSpan second) - { - return new TimeSpan(first.Ticks * second.Ticks); - } - - /// - /// Divides two TimeSpan values by dividing their ticks. - /// - public override TimeSpan Divide(TimeSpan first, TimeSpan second) - { - return new TimeSpan(first.Ticks / second.Ticks); - } - - /// - /// Compares two TimeSpan values. - /// - public override int Compare(TimeSpan first, TimeSpan second) - { - return first.CompareTo(second); - } - } - - /// - /// Implements arithmetic operations for DateTime values. - /// - public class DateTimeOperator : BaseOperator - { - /// - /// Adds two DateTime values by adding their ticks. - /// - public override DateTime Add(DateTime first, DateTime second) - { - return new DateTime(first.Ticks + second.Ticks); - } - - /// - /// Subtracts the tick values of the second DateTime from the first. - /// - public override DateTime Subtract(DateTime first, DateTime second) - { - return new DateTime(first.Ticks - second.Ticks); - } - - /// - /// Multiplies two DateTime values by multiplying their ticks. - /// - public override DateTime Multiply(DateTime first, DateTime second) - { - return new DateTime(first.Ticks * second.Ticks); - } - - /// - /// Divides two DateTime values by dividing their ticks. - /// - public override DateTime Divide(DateTime first, DateTime second) - { - return new DateTime(first.Ticks / second.Ticks); - } - - /// - /// Compares two DateTime values. - /// - public override int Compare(DateTime first, DateTime second) - { - return first.CompareTo(second); - } - } - - /// - /// Implements arithmetic operations for DateTimeOffset values. - /// - public class DateTimeOffsetOperator : BaseOperator - { - /// - /// Adds two DateTimeOffset values by adding their UTC ticks. - /// - public override DateTimeOffset Add(DateTimeOffset first, DateTimeOffset second) - { - return new DateTimeOffset(first.UtcTicks + second.UtcTicks, first.Offset); - } - - /// - /// Subtracts the UTC ticks of the second DateTimeOffset from the first. - /// - public override DateTimeOffset Subtract(DateTimeOffset first, DateTimeOffset second) - { - return new DateTimeOffset(first.UtcTicks - second.UtcTicks, first.Offset); - } - - /// - /// Multiplies two DateTimeOffset values by multiplying their UTC ticks. - /// - public override DateTimeOffset Multiply(DateTimeOffset first, DateTimeOffset second) - { - return new DateTimeOffset(first.UtcTicks * second.UtcTicks, first.Offset); - } - - /// - /// Divides two DateTimeOffset values by dividing their UTC ticks. - /// - public override DateTimeOffset Divide(DateTimeOffset first, DateTimeOffset second) - { - return new DateTimeOffset(first.UtcTicks / second.UtcTicks, first.Offset); - } - - /// - /// Compares two DateTimeOffset values. - /// - public override int Compare(DateTimeOffset first, DateTimeOffset second) - { - return first.CompareTo(second); - } + /// + /// Adds two unsigned short integers. + /// + public override ushort Add(ushort first, ushort second) + { + return (ushort)(first + second); + } + + /// + /// Subtracts the second unsigned short integer from the first. + /// + public override ushort Subtract(ushort first, ushort second) + { + return (ushort)(first - second); + } + + /// + /// Multiplies two unsigned short integers. + /// + public override ushort Multiply(ushort first, ushort second) + { + return (ushort)(first * second); + } + + /// + /// Divides the first unsigned short integer by the second. + /// + public override ushort Divide(ushort first, ushort second) + { + return (ushort)(first / second); + } + + /// + /// Compares two unsigned short integers. + /// + public override int Compare(ushort first, ushort second) + { + return first.CompareTo(second); + } +} + +/// +/// Implements arithmetic operations for long integers. +/// +public class LongOperator : BaseOperator +{ + /// + /// Adds two long integers. + /// + public override long Add(long first, long second) + { + return first + second; + } + + /// + /// Subtracts the second long integer from the first. + /// + public override long Subtract(long first, long second) + { + return first - second; + } + + /// + /// Multiplies two long integers. + /// + public override long Multiply(long first, long second) + { + return first * second; + } + + /// + /// Divides the first long integer by the second. + /// + public override long Divide(long first, long second) + { + return first / second; + } + + /// + /// Compares two long integers. + /// + public override int Compare(long first, long second) + { + return first.CompareTo(second); + } +} + +/// +/// Implements arithmetic operations for unsigned long integers. +/// +[CLSCompliant(false)] +public class ULongOperator : BaseOperator +{ + /// + /// Adds two unsigned long integers. + /// + public override ulong Add(ulong first, ulong second) + { + return first + second; + } + + /// + /// Subtracts the second unsigned long integer from the first. + /// + public override ulong Subtract(ulong first, ulong second) + { + return first - second; + } + + /// + /// Multiplies two unsigned long integers. + /// + public override ulong Multiply(ulong first, ulong second) + { + return first * second; + } + + /// + /// Divides the first unsigned long integer by the second. + /// + public override ulong Divide(ulong first, ulong second) + { + return first / second; + } + + /// + /// Compares two unsigned long integers. + /// + public override int Compare(ulong first, ulong second) + { + return first.CompareTo(second); + } +} + +/// +/// Implements arithmetic operations for floating-point numbers. +/// +public class FloatOperator : BaseOperator +{ + /// + /// Adds two float values. + /// + public override float Add(float first, float second) + { + return first + second; + } + + /// + /// Subtracts the second float value from the first. + /// + public override float Subtract(float first, float second) + { + return first - second; + } + + /// + /// Multiplies two float values. + /// + public override float Multiply(float first, float second) + { + return first * second; + } + + /// + /// Divides the first float value by the second. + /// + public override float Divide(float first, float second) + { + return first / second; + } + + /// + /// Compares two float values. + /// + public override int Compare(float first, float second) + { + return first.CompareTo(second); + } +} + +/// +/// Implements arithmetic operations for double-precision floating-point numbers. +/// +public class DoubleOperator : BaseOperator +{ + /// + /// Adds two double values. + /// + public override double Add(double first, double second) + { + return first + second; + } + + /// + /// Subtracts the second double value from the first. + /// + public override double Subtract(double first, double second) + { + return first - second; + } + + /// + /// Multiplies two double values. + /// + public override double Multiply(double first, double second) + { + return first * second; + } + + /// + /// Divides the first double value by the second. + /// + public override double Divide(double first, double second) + { + return first / second; + } + + /// + /// Compares two double values. + /// + public override int Compare(double first, double second) + { + return first.CompareTo(second); + } +} + +/// +/// Implements arithmetic operations for byte values. +/// +public class ByteOperator : BaseOperator +{ + /// + /// Adds two byte values. + /// + public override byte Add(byte first, byte second) + { + return (byte)(first + second); + } + + /// + /// Subtracts the second byte value from the first. + /// + public override byte Subtract(byte first, byte second) + { + return (byte)(first - second); + } + + /// + /// Multiplies two byte values. + /// + public override byte Multiply(byte first, byte second) + { + return (byte)(first * second); + } + + /// + /// Divides the first byte value by the second. + /// + public override byte Divide(byte first, byte second) + { + return (byte)(first / second); + } + + /// + /// Compares two byte values. + /// + public override int Compare(byte first, byte second) + { + return first.CompareTo(second); + } +} + +/// +/// Implements arithmetic operations for signed byte values. +/// +[CLSCompliant(false)] +public class SByteOperator : BaseOperator +{ + /// + /// Adds two sbyte values. + /// + public override sbyte Add(sbyte first, sbyte second) + { + return (sbyte)(first + second); + } + + /// + /// Subtracts the second sbyte value from the first. + /// + public override sbyte Subtract(sbyte first, sbyte second) + { + return (sbyte)(first - second); + } + + /// + /// Multiplies two sbyte values. + /// + public override sbyte Multiply(sbyte first, sbyte second) + { + return (sbyte)(first * second); + } + + /// + /// Divides the first sbyte value by the second. + /// + public override sbyte Divide(sbyte first, sbyte second) + { + return (sbyte)(first / second); + } + + /// + /// Compares two sbyte values. + /// + public override int Compare(sbyte first, sbyte second) + { + return first.CompareTo(second); + } +} + +/// +/// Implements arithmetic operations for decimal numbers. +/// +public class DecimalOperator : BaseOperator +{ + /// + /// Adds two decimals. + /// + public override decimal Add(decimal first, decimal second) + { + return first + second; + } + + /// + /// Subtracts the second decimal from the first. + /// + public override decimal Subtract(decimal first, decimal second) + { + return first - second; + } + + /// + /// Multiplies two decimals. + /// + public override decimal Multiply(decimal first, decimal second) + { + return first * second; + } + + /// + /// Divides the first decimal by the second. + /// + public override decimal Divide(decimal first, decimal second) + { + return first / second; + } + + /// + /// Compares two decimals. + /// + public override int Compare(decimal first, decimal second) + { + return first.CompareTo(second); + } +} + +/// +/// Implements arithmetic operations for TimeSpan values. +/// +public class TimeSpanOperator : BaseOperator +{ + /// + /// Adds two TimeSpan values. + /// + public override TimeSpan Add(TimeSpan first, TimeSpan second) + { + return first + second; + } + + /// + /// Subtracts the second TimeSpan from the first. + /// + public override TimeSpan Subtract(TimeSpan first, TimeSpan second) + { + return first - second; + } + + /// + /// Multiplies two TimeSpan values by multiplying their ticks. + /// + public override TimeSpan Multiply(TimeSpan first, TimeSpan second) + { + return new TimeSpan(first.Ticks * second.Ticks); + } + + /// + /// Divides two TimeSpan values by dividing their ticks. + /// + public override TimeSpan Divide(TimeSpan first, TimeSpan second) + { + return new TimeSpan(first.Ticks / second.Ticks); + } + + /// + /// Compares two TimeSpan values. + /// + public override int Compare(TimeSpan first, TimeSpan second) + { + return first.CompareTo(second); + } +} + +/// +/// Implements arithmetic operations for DateTime values. +/// +public class DateTimeOperator : BaseOperator +{ + /// + /// Adds two DateTime values by adding their ticks. + /// + public override DateTime Add(DateTime first, DateTime second) + { + return new DateTime(first.Ticks + second.Ticks); + } + + /// + /// Subtracts the tick values of the second DateTime from the first. + /// + public override DateTime Subtract(DateTime first, DateTime second) + { + return new DateTime(first.Ticks - second.Ticks); + } + + /// + /// Multiplies two DateTime values by multiplying their ticks. + /// + public override DateTime Multiply(DateTime first, DateTime second) + { + return new DateTime(first.Ticks * second.Ticks); + } + + /// + /// Divides two DateTime values by dividing their ticks. + /// + public override DateTime Divide(DateTime first, DateTime second) + { + return new DateTime(first.Ticks / second.Ticks); + } + + /// + /// Compares two DateTime values. + /// + public override int Compare(DateTime first, DateTime second) + { + return first.CompareTo(second); + } +} + +/// +/// Implements arithmetic operations for DateTimeOffset values. +/// +public class DateTimeOffsetOperator : BaseOperator +{ + /// + /// Adds two DateTimeOffset values by adding their UTC ticks. + /// + public override DateTimeOffset Add(DateTimeOffset first, DateTimeOffset second) + { + return new DateTimeOffset(first.UtcTicks + second.UtcTicks, first.Offset); + } + + /// + /// Subtracts the UTC ticks of the second DateTimeOffset from the first. + /// + public override DateTimeOffset Subtract(DateTimeOffset first, DateTimeOffset second) + { + return new DateTimeOffset(first.UtcTicks - second.UtcTicks, first.Offset); + } + + /// + /// Multiplies two DateTimeOffset values by multiplying their UTC ticks. + /// + public override DateTimeOffset Multiply(DateTimeOffset first, DateTimeOffset second) + { + return new DateTimeOffset(first.UtcTicks * second.UtcTicks, first.Offset); + } + + /// + /// Divides two DateTimeOffset values by dividing their UTC ticks. + /// + public override DateTimeOffset Divide(DateTimeOffset first, DateTimeOffset second) + { + return new DateTimeOffset(first.UtcTicks / second.UtcTicks, first.Offset); + } + + /// + /// Compares two DateTimeOffset values. + /// + public override int Compare(DateTimeOffset first, DateTimeOffset second) + { + return first.CompareTo(second); } } \ No newline at end of file diff --git a/Common/ISmartPointer.cs b/Common/ISmartPointer.cs index c488bf56..85e3592f 100644 --- a/Common/ISmartPointer.cs +++ b/Common/ISmartPointer.cs @@ -1,25 +1,24 @@ -namespace Ecng.Common -{ - using System; +namespace Ecng.Common; + +using System; +/// +/// Represents a smart pointer that uses reference counting to manage resource lifetimes. +/// +public interface ISmartPointer : IDisposable +{ /// - /// Represents a smart pointer that uses reference counting to manage resource lifetimes. + /// Gets the current reference counter. /// - public interface ISmartPointer : IDisposable - { - /// - /// Gets the current reference counter. - /// - int Counter { get; } + int Counter { get; } - /// - /// Increments the reference counter. - /// - void IncRef(); + /// + /// Increments the reference counter. + /// + void IncRef(); - /// - /// Decrements the reference counter. - /// - void DecRef(); - } + /// + /// Decrements the reference counter. + /// + void DecRef(); } \ No newline at end of file diff --git a/Common/IdGenerator.cs b/Common/IdGenerator.cs index 203e73f6..d629e15e 100644 --- a/Common/IdGenerator.cs +++ b/Common/IdGenerator.cs @@ -1,186 +1,185 @@ -namespace Ecng.Common +namespace Ecng.Common; + +using System; +using System.Threading; + +/// +/// Base identifier generator. +/// +public abstract class IdGenerator { - using System; - using System.Threading; + /// + /// Initializes a new instance of the class. + /// + protected IdGenerator() + { + } /// - /// Base identifier generator. + /// Gets the next identifier. /// - public abstract class IdGenerator + /// The next identifier. + public abstract long GetNextId(); +} + +/// +/// Identifier generator that automatically increments the identifier by 1. +/// +public class IncrementalIdGenerator : IdGenerator +{ + /// + /// Initializes a new instance of the class. + /// + public IncrementalIdGenerator() { - /// - /// Initializes a new instance of the class. - /// - protected IdGenerator() - { - } - - /// - /// Gets the next identifier. - /// - /// The next identifier. - public abstract long GetNextId(); } + private long _current; + /// - /// Identifier generator that automatically increments the identifier by 1. + /// Gets or sets the current identifier. /// - public class IncrementalIdGenerator : IdGenerator + public long Current { - /// - /// Initializes a new instance of the class. - /// - public IncrementalIdGenerator() - { - } - - private long _current; - - /// - /// Gets or sets the current identifier. - /// - public long Current - { - get => _current; - set => _current = value; - } - - /// - /// Gets the next identifier by incrementing the current value. - /// - /// The next identifier. - public override long GetNextId() - { - return Interlocked.Increment(ref _current); - } + get => _current; + set => _current = value; } /// - /// Identifier generator based on automatic incrementation where the initial value is the number of milliseconds - /// elapsed since the start of the day. + /// Gets the next identifier by incrementing the current value. /// - public class MillisecondIncrementalIdGenerator : IncrementalIdGenerator + /// The next identifier. + public override long GetNextId() { - /// - /// Initializes a new instance of the class. - /// - public MillisecondIncrementalIdGenerator() - { - Current = (long)(DateTime.Now - DateTime.Today).TotalMilliseconds; - } + return Interlocked.Increment(ref _current); } +} +/// +/// Identifier generator based on automatic incrementation where the initial value is the number of milliseconds +/// elapsed since the start of the day. +/// +public class MillisecondIncrementalIdGenerator : IncrementalIdGenerator +{ /// - /// Identifier generator based on automatic incrementation starting from the current Unix time in seconds (UTC). + /// Initializes a new instance of the class. /// - public class UTCIncrementalIdGenerator : IncrementalIdGenerator + public MillisecondIncrementalIdGenerator() { - /// - /// Initializes a new instance of the class. - /// - public UTCIncrementalIdGenerator() - { - Current = (long)DateTime.UtcNow.ToUnix(); - } + Current = (long)(DateTime.Now - DateTime.Today).TotalMilliseconds; } +} +/// +/// Identifier generator based on automatic incrementation starting from the current Unix time in seconds (UTC). +/// +public class UTCIncrementalIdGenerator : IncrementalIdGenerator +{ /// - /// Identifier generator based on automatic incrementation starting from the current Unix time in milliseconds (UTC). + /// Initializes a new instance of the class. /// - public class UTCMlsIncrementalIdGenerator : IncrementalIdGenerator + public UTCIncrementalIdGenerator() { - /// - /// Initializes a new instance of the class. - /// - public UTCMlsIncrementalIdGenerator() - { - Current = (long)DateTime.UtcNow.ToUnix(false); - } + Current = (long)DateTime.UtcNow.ToUnix(); } +} +/// +/// Identifier generator based on automatic incrementation starting from the current Unix time in milliseconds (UTC). +/// +public class UTCMlsIncrementalIdGenerator : IncrementalIdGenerator +{ /// - /// Identifier generator based on milliseconds. - /// Each subsequent call to returns the number of milliseconds elapsed since the instance was created. + /// Initializes a new instance of the class. /// - public class MillisecondIdGenerator : IdGenerator + public UTCMlsIncrementalIdGenerator() { - private readonly DateTime _start; - - /// - /// Initializes a new instance of the class. - /// - public MillisecondIdGenerator() - { - _start = DateTime.Now; - } - - /// - /// Gets the next identifier representing the number of milliseconds elapsed since the object was created. - /// - /// The number of milliseconds elapsed. - public override long GetNextId() - { - return (long)(DateTime.Now - _start).TotalMilliseconds; - } + Current = (long)DateTime.UtcNow.ToUnix(false); } +} + +/// +/// Identifier generator based on milliseconds. +/// Each subsequent call to returns the number of milliseconds elapsed since the instance was created. +/// +public class MillisecondIdGenerator : IdGenerator +{ + private readonly DateTime _start; /// - /// Identifier generator based on the current Unix time in milliseconds (UTC). + /// Initializes a new instance of the class. /// - public class UTCMillisecondIdGenerator : IdGenerator + public MillisecondIdGenerator() { - /// - /// Gets the next identifier based on Unix time in milliseconds. - /// - /// The Unix timestamp in milliseconds. - public override long GetNextId() - { - return (long)TimeHelper.UnixNowMls; - } + _start = DateTime.Now; } /// - /// Identifier generator based on the current Unix time in seconds (UTC). + /// Gets the next identifier representing the number of milliseconds elapsed since the object was created. /// - public class UTCSecondIdGenerator : IdGenerator + /// The number of milliseconds elapsed. + public override long GetNextId() { - /// - /// Gets the next identifier based on Unix time in seconds. - /// - /// The Unix timestamp in seconds. - public override long GetNextId() - { - return (long)TimeHelper.UnixNowS; - } + return (long)(DateTime.Now - _start).TotalMilliseconds; } +} +/// +/// Identifier generator based on the current Unix time in milliseconds (UTC). +/// +public class UTCMillisecondIdGenerator : IdGenerator +{ + /// + /// Gets the next identifier based on Unix time in milliseconds. + /// + /// The Unix timestamp in milliseconds. + public override long GetNextId() + { + return (long)TimeHelper.UnixNowMls; + } +} + +/// +/// Identifier generator based on the current Unix time in seconds (UTC). +/// +public class UTCSecondIdGenerator : IdGenerator +{ /// - /// Identifier generator that uses the current UTC ticks as the identifier. + /// Gets the next identifier based on Unix time in seconds. /// - public class TickIdGenerator : IdGenerator + /// The Unix timestamp in seconds. + public override long GetNextId() { - /// - /// Gets the next identifier based on the current UTC ticks. - /// - /// The current UTC ticks. - public override long GetNextId() - { - return DateTime.UtcNow.Ticks; - } + return (long)TimeHelper.UnixNowS; } +} +/// +/// Identifier generator that uses the current UTC ticks as the identifier. +/// +public class TickIdGenerator : IdGenerator +{ + /// + /// Gets the next identifier based on the current UTC ticks. + /// + /// The current UTC ticks. + public override long GetNextId() + { + return DateTime.UtcNow.Ticks; + } +} + +/// +/// Identifier generator that starts at the current UTC ticks and increments by 1 for each call. +/// +public class TickIncrementalIdGenerator : IncrementalIdGenerator +{ /// - /// Identifier generator that starts at the current UTC ticks and increments by 1 for each call. + /// Initializes a new instance of the class, + /// setting the initial identifier value to the current UTC ticks. /// - public class TickIncrementalIdGenerator : IncrementalIdGenerator + public TickIncrementalIdGenerator() { - /// - /// Initializes a new instance of the class, - /// setting the initial identifier value to the current UTC ticks. - /// - public TickIncrementalIdGenerator() - { - Current = DateTime.UtcNow.Ticks; - } + Current = DateTime.UtcNow.Ticks; } } \ No newline at end of file diff --git a/Common/MathHelper.cs b/Common/MathHelper.cs index 9438a13c..e25a561e 100644 --- a/Common/MathHelper.cs +++ b/Common/MathHelper.cs @@ -1,1845 +1,1844 @@ -namespace Ecng.Common +namespace Ecng.Common; + +using System; +using System.Linq; +using System.Collections.Generic; + +/// +/// Provides various mathematical helper methods and extension methods for numeric types. +/// +public static class MathHelper { - using System; - using System.Linq; - using System.Collections.Generic; + /// + /// Returns the largest multiple of less than or equal to the specified . + /// + /// The decimal value. + /// The step size. + /// The floored value. + public static decimal Floor(this decimal value, decimal step) => Math.Floor(value / step) * step; /// - /// Provides various mathematical helper methods and extension methods for numeric types. + /// Returns the smallest multiple of greater than or equal to the specified . /// - public static class MathHelper + /// The decimal value. + /// The step size. + /// The ceiled value. + public static decimal Ceiling(this decimal value, decimal step) => Math.Ceiling(value / step) * step; + + /// + /// Rounds the specified to the nearest integer. + /// + /// The decimal value. + /// The rounded value. + public static decimal Round(this decimal value) { - /// - /// Returns the largest multiple of less than or equal to the specified . - /// - /// The decimal value. - /// The step size. - /// The floored value. - public static decimal Floor(this decimal value, decimal step) => Math.Floor(value / step) * step; + return Math.Round(value); + } - /// - /// Returns the smallest multiple of greater than or equal to the specified . - /// - /// The decimal value. - /// The step size. - /// The ceiled value. - public static decimal Ceiling(this decimal value, decimal step) => Math.Ceiling(value / step) * step; + /// + /// Rounds the specified to a specified number of fractional digits. + /// + /// The decimal value. + /// The number of fractional digits. + /// The rounded value. + public static decimal Round(this decimal value, int digits) + { + return Math.Round(value, digits); + } - /// - /// Rounds the specified to the nearest integer. - /// - /// The decimal value. - /// The rounded value. - public static decimal Round(this decimal value) - { - return Math.Round(value); - } + /// + /// Rounds the specified to a specified number of fractional digits. + /// + /// The decimal value. + /// The number of fractional digits (as decimal, will be converted to int). + /// The rounded value. + public static decimal Round(this decimal value, decimal digits) + { + return Round(value, (int)digits); + } - /// - /// Rounds the specified to a specified number of fractional digits. - /// - /// The decimal value. - /// The number of fractional digits. - /// The rounded value. - public static decimal Round(this decimal value, int digits) - { - return Math.Round(value, digits); - } + /// + /// Returns the smallest integer greater than or equal to the specified . + /// + /// The decimal value. + /// The ceiled value. + public static decimal Ceiling(this decimal value) + { + return Math.Ceiling(value); + } - /// - /// Rounds the specified to a specified number of fractional digits. - /// - /// The decimal value. - /// The number of fractional digits (as decimal, will be converted to int). - /// The rounded value. - public static decimal Round(this decimal value, decimal digits) - { - return Round(value, (int)digits); - } + /// + /// Returns the largest integer less than or equal to the specified . + /// + /// The decimal value. + /// The floored value. + public static decimal Floor(this decimal value) + { + return Math.Floor(value); + } - /// - /// Returns the smallest integer greater than or equal to the specified . - /// - /// The decimal value. - /// The ceiled value. - public static decimal Ceiling(this decimal value) - { - return Math.Ceiling(value); - } + /// + /// Rounds the specified to the specified number of fractional digits using the specified rounding mode. + /// + /// The decimal value. + /// The number of fractional digits. + /// The rounding mode. + /// The rounded value. + public static decimal Round(this decimal value, int digits, MidpointRounding rounding) => Math.Round(value, digits, rounding); - /// - /// Returns the largest integer less than or equal to the specified . - /// - /// The decimal value. - /// The floored value. - public static decimal Floor(this decimal value) - { - return Math.Floor(value); - } + /// + /// Rounds the specified using the specified rounding mode. + /// + /// The decimal value. + /// The rounding mode. + /// The rounded value. + public static decimal Round(this decimal value, MidpointRounding rounding) => Math.Round(value, rounding); - /// - /// Rounds the specified to the specified number of fractional digits using the specified rounding mode. - /// - /// The decimal value. - /// The number of fractional digits. - /// The rounding mode. - /// The rounded value. - public static decimal Round(this decimal value, int digits, MidpointRounding rounding) => Math.Round(value, digits, rounding); + /// + /// Rounds the specified to the nearest multiple of , and optionally rounds to the specified number of digits. + /// + /// The decimal value. + /// The step size. + /// Optional number of fractional digits. + /// The rounding mode (defaults to ToEven). + /// The rounded value. + public static decimal Round(this decimal value, decimal step, int? digits, MidpointRounding rounding = MidpointRounding.ToEven) + { + if (step <= 0) + throw new ArgumentOutOfRangeException(nameof(step), step, "The 'step' parameter must be more than zero."); - /// - /// Rounds the specified using the specified rounding mode. - /// - /// The decimal value. - /// The rounding mode. - /// The rounded value. - public static decimal Round(this decimal value, MidpointRounding rounding) => Math.Round(value, rounding); + value = Math.Round(value / step, rounding) * step; - /// - /// Rounds the specified to the nearest multiple of , and optionally rounds to the specified number of digits. - /// - /// The decimal value. - /// The step size. - /// Optional number of fractional digits. - /// The rounding mode (defaults to ToEven). - /// The rounded value. - public static decimal Round(this decimal value, decimal step, int? digits, MidpointRounding rounding = MidpointRounding.ToEven) - { - if (step <= 0) - throw new ArgumentOutOfRangeException(nameof(step), step, "The 'step' parameter must be more than zero."); + return digits == null ? value : Math.Round(value, digits.Value); + } - value = Math.Round(value / step, rounding) * step; + /// + /// Truncates the decimal by discarding the fractional part. + /// + /// The decimal value. + /// The truncated value. + public static decimal Truncate(this decimal value) + { + return Math.Truncate(value); + } - return digits == null ? value : Math.Round(value, digits.Value); - } + /// + /// Truncates the double by discarding the fractional part. + /// + /// The double value. + /// The truncated value. + public static double Truncate(this double value) + { + return Math.Truncate(value); + } - /// - /// Truncates the decimal by discarding the fractional part. - /// - /// The decimal value. - /// The truncated value. - public static decimal Truncate(this decimal value) - { - return Math.Truncate(value); - } + /// + /// Computes the quotient and remainder of the division of two integers. + /// + /// The dividend. + /// The divisor. + /// When the method returns, contains the remainder. + /// The quotient. + public static int DivRem(this int a, int b, out int result) + { + return Math.DivRem(a, b, out result); + } - /// - /// Truncates the double by discarding the fractional part. - /// - /// The double value. - /// The truncated value. - public static double Truncate(this double value) - { - return Math.Truncate(value); - } + /// + /// Computes the quotient and remainder of the division of two long integers. + /// + /// The dividend. + /// The divisor. + /// When the method returns, contains the remainder. + /// The quotient. + public static long DivRem(this long a, long b, out long result) + { + return Math.DivRem(a, b, out result); + } - /// - /// Computes the quotient and remainder of the division of two integers. - /// - /// The dividend. - /// The divisor. - /// When the method returns, contains the remainder. - /// The quotient. - public static int DivRem(this int a, int b, out int result) - { - return Math.DivRem(a, b, out result); - } + /// + /// Rounds the double to a specified number of digits using the specified rounding mode. + /// + /// The double value. + /// The number of fractional digits. + /// The rounding mode. + /// The rounded value. + public static double Round(this double value, int digits, MidpointRounding rounding) => Math.Round(value, digits, rounding); - /// - /// Computes the quotient and remainder of the division of two long integers. - /// - /// The dividend. - /// The divisor. - /// When the method returns, contains the remainder. - /// The quotient. - public static long DivRem(this long a, long b, out long result) - { - return Math.DivRem(a, b, out result); - } + /// + /// Rounds the double using the specified rounding mode. + /// + /// The double value. + /// The rounding mode. + /// The rounded value. + public static double Round(this double value, MidpointRounding rounding) => Math.Round(value, rounding); - /// - /// Rounds the double to a specified number of digits using the specified rounding mode. - /// - /// The double value. - /// The number of fractional digits. - /// The rounding mode. - /// The rounded value. - public static double Round(this double value, int digits, MidpointRounding rounding) => Math.Round(value, digits, rounding); + /// + /// Returns the full product of two integers. + /// + /// The first integer. + /// The second integer. + /// The product as a long. + public static long BigMul(this int x, int y) + { + return Math.BigMul(x, y); + } - /// - /// Rounds the double using the specified rounding mode. - /// - /// The double value. - /// The rounding mode. - /// The rounded value. - public static double Round(this double value, MidpointRounding rounding) => Math.Round(value, rounding); + /// + /// Returns the smallest integer greater than or equal to the specified double as a long. + /// + /// The double value. + /// The ceiled value as long. + public static long Ceiling(this double value) + { + return (long)Math.Ceiling(value); + } - /// - /// Returns the full product of two integers. - /// - /// The first integer. - /// The second integer. - /// The product as a long. - public static long BigMul(this int x, int y) - { - return Math.BigMul(x, y); - } + /// + /// Returns the largest integer less than or equal to the specified double as a long. + /// + /// The double value. + /// The floored value as long. + public static long Floor(this double value) + { + return (long)Math.Floor(value); + } - /// - /// Returns the smallest integer greater than or equal to the specified double as a long. - /// - /// The double value. - /// The ceiled value as long. - public static long Ceiling(this double value) - { - return (long)Math.Ceiling(value); - } + /// + /// Returns the largest multiple of less than or equal to the integer . + /// + /// The integer value. + /// The step size. + /// The floored value. + public static int Floor(this int value, int step) => (value >= 0 ? value : value - step + 1) / step * step; - /// - /// Returns the largest integer less than or equal to the specified double as a long. - /// - /// The double value. - /// The floored value as long. - public static long Floor(this double value) - { - return (long)Math.Floor(value); - } + /// + /// Returns the largest multiple of less than or equal to the long integer . + /// + /// The long integer value. + /// The step size. + /// The floored value. + public static long Floor(this long value, long step) => (value >= 0 ? value : value - step + 1) / step * step; - /// - /// Returns the largest multiple of less than or equal to the integer . - /// - /// The integer value. - /// The step size. - /// The floored value. - public static int Floor(this int value, int step) => (value >= 0 ? value : value - step + 1) / step * step; + /// + /// Returns the largest multiple of less than or equal to the float . + /// + /// The float value. + /// The step size. + /// The floored value. + public static float Floor(this float value, float step) => (float)(Math.Floor((double)value / step) * step); - /// - /// Returns the largest multiple of less than or equal to the long integer . - /// - /// The long integer value. - /// The step size. - /// The floored value. - public static long Floor(this long value, long step) => (value >= 0 ? value : value - step + 1) / step * step; + /// + /// Returns the largest multiple of less than or equal to the double . + /// + /// The double value. + /// The step size. + /// The floored value. + public static double Floor(this double value, double step) => Math.Floor(value / step) * step; - /// - /// Returns the largest multiple of less than or equal to the float . - /// - /// The float value. - /// The step size. - /// The floored value. - public static float Floor(this float value, float step) => (float)(Math.Floor((double)value / step) * step); + /// + /// Returns the absolute value of the short . + /// + /// The short integer. + /// The absolute value. + public static short Abs(this short value) + { + return Math.Abs(value); + } - /// - /// Returns the largest multiple of less than or equal to the double . - /// - /// The double value. - /// The step size. - /// The floored value. - public static double Floor(this double value, double step) => Math.Floor(value / step) * step; + /// + /// Returns the absolute value of the integer . + /// + /// The integer value. + /// The absolute value. + public static int Abs(this int value) + { + return Math.Abs(value); + } - /// - /// Returns the absolute value of the short . - /// - /// The short integer. - /// The absolute value. - public static short Abs(this short value) - { - return Math.Abs(value); - } + /// + /// Returns the absolute value of the long integer . + /// + /// The long integer. + /// The absolute value. + public static long Abs(this long value) + { + return Math.Abs(value); + } - /// - /// Returns the absolute value of the integer . - /// - /// The integer value. - /// The absolute value. - public static int Abs(this int value) - { - return Math.Abs(value); - } + /// + /// Returns the absolute value of the sbyte . + /// + /// The sbyte value. + /// The absolute value. + [CLSCompliant(false)] + public static sbyte Abs(this sbyte value) + { + return Math.Abs(value); + } - /// - /// Returns the absolute value of the long integer . - /// - /// The long integer. - /// The absolute value. - public static long Abs(this long value) - { - return Math.Abs(value); - } + /// + /// Returns the absolute value of the float . + /// + /// The float value. + /// The absolute value. + public static float Abs(this float value) + { + return Math.Abs(value); + } - /// - /// Returns the absolute value of the sbyte . - /// - /// The sbyte value. - /// The absolute value. - [CLSCompliant(false)] - public static sbyte Abs(this sbyte value) - { - return Math.Abs(value); - } + /// + /// Returns the absolute value of the double . + /// + /// The double value. + /// The absolute value. + public static double Abs(this double value) + { + return Math.Abs(value); + } - /// - /// Returns the absolute value of the float . - /// - /// The float value. - /// The absolute value. - public static float Abs(this float value) - { - return Math.Abs(value); - } + /// + /// Returns the absolute value of the decimal . + /// + /// The decimal value. + /// The absolute value. + public static decimal Abs(this decimal value) + { + return Math.Abs(value); + } - /// - /// Returns the absolute value of the double . - /// - /// The double value. - /// The absolute value. - public static double Abs(this double value) - { - return Math.Abs(value); - } + /// + /// Returns a TimeSpan whose ticks are the absolute value of the original TimeSpan's ticks. + /// + /// The TimeSpan value. + /// The TimeSpan with absolute ticks. + public static TimeSpan Abs(this TimeSpan value) + { + return value.Ticks.Abs().To(); + } - /// - /// Returns the absolute value of the decimal . - /// - /// The decimal value. - /// The absolute value. - public static decimal Abs(this decimal value) - { - return Math.Abs(value); - } + /// + /// Returns the smaller of two short values. + /// + /// The first short value. + /// The second short value. + /// The minimum value. + public static short Min(this short value1, short value2) + { + return Math.Min(value1, value2); + } - /// - /// Returns a TimeSpan whose ticks are the absolute value of the original TimeSpan's ticks. - /// - /// The TimeSpan value. - /// The TimeSpan with absolute ticks. - public static TimeSpan Abs(this TimeSpan value) - { - return value.Ticks.Abs().To(); - } + /// + /// Returns the smaller of two ushort values. + /// + /// The first ushort value. + /// The second ushort value. + /// The minimum value. + [CLSCompliant(false)] + public static ushort Min(this ushort value1, ushort value2) + { + return Math.Min(value1, value2); + } - /// - /// Returns the smaller of two short values. - /// - /// The first short value. - /// The second short value. - /// The minimum value. - public static short Min(this short value1, short value2) - { - return Math.Min(value1, value2); - } + /// + /// Returns the smaller of two integer values. + /// + /// The first integer value. + /// The second integer value. + /// The minimum value. + public static int Min(this int value1, int value2) + { + return Math.Min(value1, value2); + } - /// - /// Returns the smaller of two ushort values. - /// - /// The first ushort value. - /// The second ushort value. - /// The minimum value. - [CLSCompliant(false)] - public static ushort Min(this ushort value1, ushort value2) - { - return Math.Min(value1, value2); - } + /// + /// Returns the smaller of two uint values. + /// + /// The first uint value. + /// The second uint value. + /// The minimum value. + [CLSCompliant(false)] + public static uint Min(this uint value1, uint value2) + { + return Math.Min(value1, value2); + } - /// - /// Returns the smaller of two integer values. - /// - /// The first integer value. - /// The second integer value. - /// The minimum value. - public static int Min(this int value1, int value2) - { - return Math.Min(value1, value2); - } + /// + /// Returns the smaller of two long values. + /// + /// The first long value. + /// The second long value. + /// The minimum value. + public static long Min(this long value1, long value2) + { + return Math.Min(value1, value2); + } - /// - /// Returns the smaller of two uint values. - /// - /// The first uint value. - /// The second uint value. - /// The minimum value. - [CLSCompliant(false)] - public static uint Min(this uint value1, uint value2) - { - return Math.Min(value1, value2); - } + /// + /// Returns the smaller of two ulong values. + /// + /// The first ulong value. + /// The second ulong value. + /// The minimum value. + [CLSCompliant(false)] + public static ulong Min(this ulong value1, ulong value2) + { + return Math.Min(value1, value2); + } - /// - /// Returns the smaller of two long values. - /// - /// The first long value. - /// The second long value. - /// The minimum value. - public static long Min(this long value1, long value2) - { - return Math.Min(value1, value2); - } + /// + /// Returns the smaller of two sbyte values. + /// + /// The first sbyte value. + /// The second sbyte value. + /// The minimum value. + [CLSCompliant(false)] + public static sbyte Min(this sbyte value1, sbyte value2) + { + return Math.Min(value1, value2); + } - /// - /// Returns the smaller of two ulong values. - /// - /// The first ulong value. - /// The second ulong value. - /// The minimum value. - [CLSCompliant(false)] - public static ulong Min(this ulong value1, ulong value2) - { - return Math.Min(value1, value2); - } + /// + /// Returns the smaller of two byte values. + /// + /// The first byte value. + /// The second byte value. + /// The minimum value. + public static byte Min(this byte value1, byte value2) + { + return Math.Min(value1, value2); + } - /// - /// Returns the smaller of two sbyte values. - /// - /// The first sbyte value. - /// The second sbyte value. - /// The minimum value. - [CLSCompliant(false)] - public static sbyte Min(this sbyte value1, sbyte value2) - { - return Math.Min(value1, value2); - } + /// + /// Returns the smaller of two float values. + /// + /// The first float value. + /// The second float value. + /// The minimum value. + public static float Min(this float value1, float value2) + { + return Math.Min(value1, value2); + } - /// - /// Returns the smaller of two byte values. - /// - /// The first byte value. - /// The second byte value. - /// The minimum value. - public static byte Min(this byte value1, byte value2) - { - return Math.Min(value1, value2); - } + /// + /// Returns the smaller of two double values. + /// + /// The first double value. + /// The second double value. + /// The minimum value. + public static double Min(this double value1, double value2) + { + return Math.Min(value1, value2); + } - /// - /// Returns the smaller of two float values. - /// - /// The first float value. - /// The second float value. - /// The minimum value. - public static float Min(this float value1, float value2) - { - return Math.Min(value1, value2); - } + /// + /// Returns the smaller of two decimal values. + /// + /// The first decimal value. + /// The second decimal value. + /// The minimum value. + public static decimal Min(this decimal value1, decimal value2) + { + return Math.Min(value1, value2); + } - /// - /// Returns the smaller of two double values. - /// - /// The first double value. - /// The second double value. - /// The minimum value. - public static double Min(this double value1, double value2) - { - return Math.Min(value1, value2); - } + /// + /// Returns the earlier TimeSpan. + /// + /// The first TimeSpan. + /// The second TimeSpan. + /// The minimum TimeSpan. + public static TimeSpan Min(this TimeSpan value1, TimeSpan value2) + { + return value1 <= value2 ? value1 : value2; + } - /// - /// Returns the smaller of two decimal values. - /// - /// The first decimal value. - /// The second decimal value. - /// The minimum value. - public static decimal Min(this decimal value1, decimal value2) - { - return Math.Min(value1, value2); - } + /// + /// Returns the earlier DateTime. + /// + /// The first DateTime. + /// The second DateTime. + /// The minimum DateTime. + public static DateTime Min(this DateTime value1, DateTime value2) + { + return value1 <= value2 ? value1 : value2; + } - /// - /// Returns the earlier TimeSpan. - /// - /// The first TimeSpan. - /// The second TimeSpan. - /// The minimum TimeSpan. - public static TimeSpan Min(this TimeSpan value1, TimeSpan value2) - { - return value1 <= value2 ? value1 : value2; - } + /// + /// Returns the minimum of two DateTimeOffset values. + /// + /// The first DateTimeOffset value. + /// The second DateTimeOffset value. + /// The smaller DateTimeOffset value. + public static DateTimeOffset Min(this DateTimeOffset value1, DateTimeOffset value2) + { + return value1 <= value2 ? value1 : value2; + } - /// - /// Returns the earlier DateTime. - /// - /// The first DateTime. - /// The second DateTime. - /// The minimum DateTime. - public static DateTime Min(this DateTime value1, DateTime value2) - { - return value1 <= value2 ? value1 : value2; - } + /// + /// Returns the maximum of two short values. + /// + /// The first short value. + /// The second short value. + /// The larger short value. + public static short Max(this short value1, short value2) + { + return Math.Max(value1, value2); + } - /// - /// Returns the minimum of two DateTimeOffset values. - /// - /// The first DateTimeOffset value. - /// The second DateTimeOffset value. - /// The smaller DateTimeOffset value. - public static DateTimeOffset Min(this DateTimeOffset value1, DateTimeOffset value2) - { - return value1 <= value2 ? value1 : value2; - } + /// + /// Returns the maximum of two ushort values. + /// + /// The first ushort value. + /// The second ushort value. + /// The larger ushort value. + [CLSCompliant(false)] + public static ushort Max(this ushort value1, ushort value2) + { + return Math.Max(value1, value2); + } - /// - /// Returns the maximum of two short values. - /// - /// The first short value. - /// The second short value. - /// The larger short value. - public static short Max(this short value1, short value2) - { - return Math.Max(value1, value2); - } + /// + /// Returns the maximum of two int values. + /// + /// The first int value. + /// The second int value. + /// The larger int value. + public static int Max(this int value1, int value2) + { + return Math.Max(value1, value2); + } - /// - /// Returns the maximum of two ushort values. - /// - /// The first ushort value. - /// The second ushort value. - /// The larger ushort value. - [CLSCompliant(false)] - public static ushort Max(this ushort value1, ushort value2) - { - return Math.Max(value1, value2); - } + /// + /// Returns the maximum of two uint values. + /// + /// The first uint value. + /// The second uint value. + /// The larger uint value. + [CLSCompliant(false)] + public static uint Max(this uint value1, uint value2) + { + return Math.Max(value1, value2); + } - /// - /// Returns the maximum of two int values. - /// - /// The first int value. - /// The second int value. - /// The larger int value. - public static int Max(this int value1, int value2) - { - return Math.Max(value1, value2); - } + /// + /// Returns the maximum of two long values. + /// + /// The first long value. + /// The second long value. + /// The larger long value. + public static long Max(this long value1, long value2) + { + return Math.Max(value1, value2); + } - /// - /// Returns the maximum of two uint values. - /// - /// The first uint value. - /// The second uint value. - /// The larger uint value. - [CLSCompliant(false)] - public static uint Max(this uint value1, uint value2) - { - return Math.Max(value1, value2); - } + /// + /// Returns the maximum of two ulong values. + /// + /// The first ulong value. + /// The second ulong value. + /// The larger ulong value. + [CLSCompliant(false)] + public static ulong Max(this ulong value1, ulong value2) + { + return Math.Max(value1, value2); + } - /// - /// Returns the maximum of two long values. - /// - /// The first long value. - /// The second long value. - /// The larger long value. - public static long Max(this long value1, long value2) - { - return Math.Max(value1, value2); - } + /// + /// Returns the maximum of two sbyte values. + /// + /// The first sbyte value. + /// The second sbyte value. + /// The larger sbyte value. + [CLSCompliant(false)] + public static sbyte Max(this sbyte value1, sbyte value2) + { + return Math.Max(value1, value2); + } - /// - /// Returns the maximum of two ulong values. - /// - /// The first ulong value. - /// The second ulong value. - /// The larger ulong value. - [CLSCompliant(false)] - public static ulong Max(this ulong value1, ulong value2) - { - return Math.Max(value1, value2); - } + /// + /// Returns the maximum of two byte values. + /// + /// The first byte value. + /// The second byte value. + /// The larger byte value. + public static byte Max(this byte value1, byte value2) + { + return Math.Max(value1, value2); + } - /// - /// Returns the maximum of two sbyte values. - /// - /// The first sbyte value. - /// The second sbyte value. - /// The larger sbyte value. - [CLSCompliant(false)] - public static sbyte Max(this sbyte value1, sbyte value2) - { - return Math.Max(value1, value2); - } + /// + /// Returns the smaller of two float values. + /// Note: Implementation calls Math.Min. + /// + /// The first float value. + /// The second float value. + /// The smaller float value. + public static float Max(this float value1, float value2) + { + return Math.Min(value1, value2); + } - /// - /// Returns the maximum of two byte values. - /// - /// The first byte value. - /// The second byte value. - /// The larger byte value. - public static byte Max(this byte value1, byte value2) - { - return Math.Max(value1, value2); - } + /// + /// Returns the maximum of two double values. + /// + /// The first double value. + /// The second double value. + /// The larger double value. + public static double Max(this double value1, double value2) + { + return Math.Max(value1, value2); + } - /// - /// Returns the smaller of two float values. - /// Note: Implementation calls Math.Min. - /// - /// The first float value. - /// The second float value. - /// The smaller float value. - public static float Max(this float value1, float value2) - { - return Math.Min(value1, value2); - } + /// + /// Returns the maximum of two decimal values. + /// + /// The first decimal value. + /// The second decimal value. + /// The larger decimal value. + public static decimal Max(this decimal value1, decimal value2) + { + return Math.Max(value1, value2); + } - /// - /// Returns the maximum of two double values. - /// - /// The first double value. - /// The second double value. - /// The larger double value. - public static double Max(this double value1, double value2) - { - return Math.Max(value1, value2); - } + /// + /// Returns the maximum of two TimeSpan values. + /// + /// The first TimeSpan value. + /// The second TimeSpan value. + /// The longer TimeSpan. + public static TimeSpan Max(this TimeSpan value1, TimeSpan value2) + { + return value1 >= value2 ? value1 : value2; + } - /// - /// Returns the maximum of two decimal values. - /// - /// The first decimal value. - /// The second decimal value. - /// The larger decimal value. - public static decimal Max(this decimal value1, decimal value2) - { - return Math.Max(value1, value2); - } + /// + /// Returns the maximum of two DateTime values. + /// + /// The first DateTime value. + /// The second DateTime value. + /// The later DateTime. + public static DateTime Max(this DateTime value1, DateTime value2) + { + return value1 >= value2 ? value1 : value2; + } - /// - /// Returns the maximum of two TimeSpan values. - /// - /// The first TimeSpan value. - /// The second TimeSpan value. - /// The longer TimeSpan. - public static TimeSpan Max(this TimeSpan value1, TimeSpan value2) - { - return value1 >= value2 ? value1 : value2; - } + /// + /// Returns the maximum of two DateTimeOffset values. + /// + /// The first DateTimeOffset value. + /// The second DateTimeOffset value. + /// The later DateTimeOffset. + public static DateTimeOffset Max(this DateTimeOffset value1, DateTimeOffset value2) + { + return value1 >= value2 ? value1 : value2; + } - /// - /// Returns the maximum of two DateTime values. - /// - /// The first DateTime value. - /// The second DateTime value. - /// The later DateTime. - public static DateTime Max(this DateTime value1, DateTime value2) - { - return value1 >= value2 ? value1 : value2; - } + /// + /// Rounds the double value to the nearest integer. + /// + /// The double value to round. + /// The rounded double value. + public static double Round(this double value) + { + return Math.Round(value); + } - /// - /// Returns the maximum of two DateTimeOffset values. - /// - /// The first DateTimeOffset value. - /// The second DateTimeOffset value. - /// The later DateTimeOffset. - public static DateTimeOffset Max(this DateTimeOffset value1, DateTimeOffset value2) - { - return value1 >= value2 ? value1 : value2; - } + /// + /// Rounds the double value to the specified number of fractional digits. + /// + /// The double value to round. + /// The number of decimal places to round to. + /// The rounded double value. + public static double Round(this double value, int digits) + { + return Math.Round(value, digits); + } - /// - /// Rounds the double value to the nearest integer. - /// - /// The double value to round. - /// The rounded double value. - public static double Round(this double value) - { - return Math.Round(value); - } + /// + /// Returns the square root of the double value. + /// + /// The double value. + /// The square root of the value. + public static double Sqrt(this double value) + { + return Math.Sqrt(value); + } - /// - /// Rounds the double value to the specified number of fractional digits. - /// - /// The double value to round. - /// The number of decimal places to round to. - /// The rounded double value. - public static double Round(this double value, int digits) - { - return Math.Round(value, digits); - } + /// + /// Raises a decimal number to the power of another decimal number. + /// + /// The base decimal value. + /// The exponent decimal value. + /// The resulting decimal value. + public static decimal Pow(this decimal x, decimal y) + { + return (decimal)Math.Pow((double)x, (double)y); + } - /// - /// Returns the square root of the double value. - /// - /// The double value. - /// The square root of the value. - public static double Sqrt(this double value) - { - return Math.Sqrt(value); - } + /// + /// Raises an int number to the power of an int exponent. + /// + /// The base int value. + /// The exponent int value. + /// The resulting int value. + public static int Pow(this int x, int y) + { + return (int)Math.Pow(x, y); + } - /// - /// Raises a decimal number to the power of another decimal number. - /// - /// The base decimal value. - /// The exponent decimal value. - /// The resulting decimal value. - public static decimal Pow(this decimal x, decimal y) - { - return (decimal)Math.Pow((double)x, (double)y); - } + /// + /// Raises a double number to the power of a double exponent. + /// + /// The base double value. + /// The exponent double value. + /// The resulting double value. + public static double Pow(this double x, double y) + { + return Math.Pow(x, y); + } - /// - /// Raises an int number to the power of an int exponent. - /// - /// The base int value. - /// The exponent int value. - /// The resulting int value. - public static int Pow(this int x, int y) - { - return (int)Math.Pow(x, y); - } + /// + /// Returns the angle whose cosine is the specified value. + /// + /// A double value representing a cosine. + /// The angle, in radians, whose cosine is the specified value. + public static double Acos(this double value) + { + return Math.Acos(value); + } - /// - /// Raises a double number to the power of a double exponent. - /// - /// The base double value. - /// The exponent double value. - /// The resulting double value. - public static double Pow(this double x, double y) - { - return Math.Pow(x, y); - } + /// + /// Returns the angle whose cosine is the specified decimal value. + /// + /// A decimal value representing a cosine. + /// The angle, in radians, whose cosine is the specified value. + public static decimal Acos(this decimal value) + { + return (decimal)Math.Acos((double)value); + } - /// - /// Returns the angle whose cosine is the specified value. - /// - /// A double value representing a cosine. - /// The angle, in radians, whose cosine is the specified value. - public static double Acos(this double value) - { - return Math.Acos(value); - } + /// + /// Returns the angle whose sine is the specified value. + /// + /// A double value representing a sine. + /// The angle, in radians, whose sine is the specified value. + public static double Asin(this double value) + { + return Math.Asin(value); + } - /// - /// Returns the angle whose cosine is the specified decimal value. - /// - /// A decimal value representing a cosine. - /// The angle, in radians, whose cosine is the specified value. - public static decimal Acos(this decimal value) - { - return (decimal)Math.Acos((double)value); - } + /// + /// Returns the angle whose sine is the specified decimal value. + /// + /// A decimal value representing a sine. + /// The angle, in radians, whose sine is the specified value. + public static decimal Asin(this decimal value) + { + return (decimal)Math.Asin((double)value); + } - /// - /// Returns the angle whose sine is the specified value. - /// - /// A double value representing a sine. - /// The angle, in radians, whose sine is the specified value. - public static double Asin(this double value) - { - return Math.Asin(value); - } + /// + /// Returns the angle whose tangent is the specified value. + /// + /// A double value representing a tangent. + /// The angle, in radians, whose tangent is the specified value. + public static double Atan(this double value) + { + return Math.Atan(value); + } - /// - /// Returns the angle whose sine is the specified decimal value. - /// - /// A decimal value representing a sine. - /// The angle, in radians, whose sine is the specified value. - public static decimal Asin(this decimal value) - { - return (decimal)Math.Asin((double)value); - } + /// + /// Returns the angle whose tangent is the specified decimal value. + /// + /// A decimal value representing a tangent. + /// The angle, in radians, whose tangent is the specified value. + public static decimal Atan(this decimal value) + { + return (decimal)Math.Atan((double)value); + } - /// - /// Returns the angle whose tangent is the specified value. - /// - /// A double value representing a tangent. - /// The angle, in radians, whose tangent is the specified value. - public static double Atan(this double value) - { - return Math.Atan(value); - } + /// + /// Returns the angle whose tangent is the quotient of two specified double numbers. + /// + /// The numerator. + /// The denominator. + /// The angle, in radians, whose tangent is the quotient of x and y. + public static double Asin(this double x, double y) + { + return Math.Atan2(x, y); + } - /// - /// Returns the angle whose tangent is the specified decimal value. - /// - /// A decimal value representing a tangent. - /// The angle, in radians, whose tangent is the specified value. - public static decimal Atan(this decimal value) - { - return (decimal)Math.Atan((double)value); - } + /// + /// Returns the angle whose tangent is the quotient of two specified decimal numbers. + /// + /// The numerator. + /// The denominator. + /// The angle, in radians, whose tangent is the quotient of x and y. + public static decimal Asin(this decimal x, decimal y) + { + return (decimal)Math.Atan2((double)x, (double)y); + } - /// - /// Returns the angle whose tangent is the quotient of two specified double numbers. - /// - /// The numerator. - /// The denominator. - /// The angle, in radians, whose tangent is the quotient of x and y. - public static double Asin(this double x, double y) - { - return Math.Atan2(x, y); - } + /// + /// Returns the cosine of the specified double angle. + /// + /// An angle, measured in radians. + /// The cosine of the angle. + public static double Cos(this double value) + { + return Math.Cos(value); + } - /// - /// Returns the angle whose tangent is the quotient of two specified decimal numbers. - /// - /// The numerator. - /// The denominator. - /// The angle, in radians, whose tangent is the quotient of x and y. - public static decimal Asin(this decimal x, decimal y) - { - return (decimal)Math.Atan2((double)x, (double)y); - } + /// + /// Returns the cosine of the specified decimal angle. + /// + /// An angle, measured in radians (as decimal). + /// The cosine of the angle as a decimal. + public static decimal Cos(this decimal value) + { + return (decimal)Math.Cos((double)value); + } - /// - /// Returns the cosine of the specified double angle. - /// - /// An angle, measured in radians. - /// The cosine of the angle. - public static double Cos(this double value) - { - return Math.Cos(value); - } + /// + /// Returns the hyperbolic cosine of the specified double angle. + /// + /// An angle, measured in radians. + /// The hyperbolic cosine of the angle. + public static double Cosh(this double value) + { + return Math.Cosh(value); + } - /// - /// Returns the cosine of the specified decimal angle. - /// - /// An angle, measured in radians (as decimal). - /// The cosine of the angle as a decimal. - public static decimal Cos(this decimal value) - { - return (decimal)Math.Cos((double)value); - } + /// + /// Returns the hyperbolic cosine of the specified decimal angle. + /// + /// An angle, measured in radians (as decimal). + /// The hyperbolic cosine of the angle as a decimal. + public static decimal Cosh(this decimal value) + { + return (decimal)Math.Cosh((double)value); + } - /// - /// Returns the hyperbolic cosine of the specified double angle. - /// - /// An angle, measured in radians. - /// The hyperbolic cosine of the angle. - public static double Cosh(this double value) - { - return Math.Cosh(value); - } + /// + /// Returns the sine of the specified double angle. + /// + /// An angle, measured in radians. + /// The sine of the angle. + public static double Sin(this double value) + { + return Math.Sin(value); + } - /// - /// Returns the hyperbolic cosine of the specified decimal angle. - /// - /// An angle, measured in radians (as decimal). - /// The hyperbolic cosine of the angle as a decimal. - public static decimal Cosh(this decimal value) - { - return (decimal)Math.Cosh((double)value); - } + /// + /// Returns the sine of the specified decimal angle. + /// + /// An angle, measured in radians (as decimal). + /// The sine of the angle as a decimal. + public static decimal Sin(this decimal value) + { + return (decimal)Math.Sin((double)value); + } + + /// + /// Returns the hyperbolic sine of the specified double angle. + /// + /// An angle, measured in radians. + /// The hyperbolic sine of the angle. + public static double Sinh(this double value) + { + return Math.Sinh(value); + } + + /// + /// Returns the hyperbolic sine of the specified decimal angle. + /// + /// An angle, measured in radians (as decimal). + /// The hyperbolic sine of the angle as a decimal. + public static decimal Sinh(this decimal value) + { + return (decimal)Math.Sinh((double)value); + } + + /// + /// Returns the tangent of the specified double angle. + /// + /// An angle, measured in radians. + /// The tangent of the angle. + public static double Tan(this double value) + { + return Math.Tan(value); + } + + /// + /// Returns the tangent of the specified decimal angle. + /// + /// An angle, measured in radians (as decimal). + /// The tangent of the angle as a decimal. + public static decimal Tan(this decimal value) + { + return (decimal)Math.Tan((double)value); + } + + /// + /// Returns the hyperbolic tangent of the specified double angle. + /// + /// An angle, measured in radians. + /// The hyperbolic tangent of the angle. + public static double Tanh(this double value) + { + return Math.Tanh(value); + } + + /// + /// Returns the hyperbolic tangent of the specified decimal angle. + /// + /// An angle, measured in radians (as decimal). + /// The hyperbolic tangent of the angle as a decimal. + public static decimal Tanh(this decimal value) + { + return (decimal)Math.Tanh((double)value); + } + + /// + /// Returns e raised to the specified double power. + /// + /// A double exponent. + /// The value of e raised to the specified power. + public static double Exp(this double value) + { + return Math.Exp(value); + } + + /// + /// Returns e raised to the specified decimal power. + /// + /// A decimal exponent. + /// The value of e raised to the specified power as a decimal. + public static decimal Exp(this decimal value) + { + return (decimal)Math.Exp((double)value); + } + + /// + /// Returns the IEEE remainder of two double values. + /// + /// The dividend. + /// The divisor. + /// The remainder after division. + public static double Remainder(this double x, double y) + { + return Math.IEEERemainder(x, y); + } + + /// + /// Returns the IEEE remainder of two decimal values. + /// + /// The dividend. + /// The divisor. + /// The remainder after division as a decimal. + public static decimal Remainder(this decimal x, decimal y) + { + return (decimal)Math.IEEERemainder((double)x, (double)y); + } + + /// + /// Returns the logarithm of a double value in the specified base. + /// + /// The double value. + /// The base of the logarithm. + /// The logarithm of value in the specified base. + public static double Log(this double value, double newBase) + { + return Math.Log(value, newBase); + } + + /// + /// Returns the logarithm of a decimal value in the specified base. + /// + /// The decimal value. + /// The base of the logarithm. + /// The logarithm of the value in the specified base as a decimal. + public static decimal Log(this decimal value, decimal newBase) + { + return (decimal)Math.Log((double)value, (double)newBase); + } + + /// + /// Returns the natural logarithm of a double value. + /// + /// The double value. + /// The natural logarithm of the value. + public static double Log(this double value) + { + return Math.Log(value); + } + + /// + /// Returns the natural logarithm of a decimal value. + /// + /// The decimal value. + /// The natural logarithm of the value as a decimal. + public static decimal Log(this decimal value) + { + return (decimal)Math.Log((double)value); + } + + /// + /// Returns the base 10 logarithm of a double value. + /// + /// The double value. + /// The base 10 logarithm of the value. + public static double Log10(this double value) + { + return Math.Log10(value); + } + + /// + /// Returns the base 10 logarithm of a decimal value. + /// + /// The decimal value. + /// The base 10 logarithm of the value as a decimal. + public static decimal Log10(this decimal value) + { + return (decimal)Math.Log10((double)value); + } + + /// + /// Returns the sign of a short value. + /// + /// The short value. + /// -1, 0, or 1 depending on the sign of the value. + public static int Sign(this short value) + { + return Math.Sign(value); + } + + /// + /// Returns the sign of an int value. + /// + /// The int value. + /// -1, 0, or 1 depending on the sign of the value. + public static int Sign(this int value) + { + return Math.Sign(value); + } - /// - /// Returns the sine of the specified double angle. - /// - /// An angle, measured in radians. - /// The sine of the angle. - public static double Sin(this double value) - { - return Math.Sin(value); - } + /// + /// Returns the sign of a long value. + /// + /// The long value. + /// -1, 0, or 1 depending on the sign of the value. + public static int Sign(this long value) + { + return Math.Sign(value); + } - /// - /// Returns the sine of the specified decimal angle. - /// - /// An angle, measured in radians (as decimal). - /// The sine of the angle as a decimal. - public static decimal Sin(this decimal value) - { - return (decimal)Math.Sin((double)value); - } + /// + /// Returns the sign of a sbyte value. + /// + /// The sbyte value. + /// -1, 0, or 1 depending on the sign of the value. + [CLSCompliant(false)] + public static int Sign(this sbyte value) + { + return Math.Sign(value); + } - /// - /// Returns the hyperbolic sine of the specified double angle. - /// - /// An angle, measured in radians. - /// The hyperbolic sine of the angle. - public static double Sinh(this double value) - { - return Math.Sinh(value); - } + /// + /// Returns the sign of a float value. + /// + /// The float value. + /// -1, 0, or 1 depending on the sign of the value. + public static int Sign(this float value) + { + return Math.Sign(value); + } - /// - /// Returns the hyperbolic sine of the specified decimal angle. - /// - /// An angle, measured in radians (as decimal). - /// The hyperbolic sine of the angle as a decimal. - public static decimal Sinh(this decimal value) - { - return (decimal)Math.Sinh((double)value); - } + /// + /// Returns the sign of a double value. + /// + /// The double value. + /// -1, 0, or 1 depending on the sign of the value. + public static int Sign(this double value) + { + return Math.Sign(value); + } - /// - /// Returns the tangent of the specified double angle. - /// - /// An angle, measured in radians. - /// The tangent of the angle. - public static double Tan(this double value) - { - return Math.Tan(value); - } + /// + /// Returns the sign of a decimal value. + /// + /// The decimal value. + /// -1, 0, or 1 depending on the sign of the value. + public static int Sign(this decimal value) + { + return Math.Sign(value); + } - /// - /// Returns the tangent of the specified decimal angle. - /// - /// An angle, measured in radians (as decimal). - /// The tangent of the angle as a decimal. - public static decimal Tan(this decimal value) - { - return (decimal)Math.Tan((double)value); - } + /// + /// Returns the sign of a TimeSpan value. + /// + /// The TimeSpan value. + /// -1, 0, or 1 depending on the sign of the Ticks. + public static int Sign(this TimeSpan value) + { + return value.Ticks.Sign(); + } - /// - /// Returns the hyperbolic tangent of the specified double angle. - /// - /// An angle, measured in radians. - /// The hyperbolic tangent of the angle. - public static double Tanh(this double value) - { - return Math.Tanh(value); - } + /// + /// Returns the largest integer less than or equal to the float value. + /// + /// The float value. + /// The floor integer. + public static int Floor(this float value) + { + return (int)Math.Floor(value); + } - /// - /// Returns the hyperbolic tangent of the specified decimal angle. - /// - /// An angle, measured in radians (as decimal). - /// The hyperbolic tangent of the angle as a decimal. - public static decimal Tanh(this decimal value) - { - return (decimal)Math.Tanh((double)value); - } + /// + /// Returns the smallest integer greater than or equal to the float value. + /// + /// The float value. + /// The ceiling integer. + public static int Ceiling(this float value) + { + return (int)Math.Ceiling(value); + } - /// - /// Returns e raised to the specified double power. - /// - /// A double exponent. - /// The value of e raised to the specified power. - public static double Exp(this double value) - { - return Math.Exp(value); - } + /// + /// Extracts the high and low parts of a long value. + /// + /// The long value. + /// An array with two integers: the low and high parts. + public static int[] GetParts(this long value) + { + var high = (int)((value >> 32) & 0xFFFFFFFF); + var low = (int)(value & 0xFFFFFFFF); - /// - /// Returns e raised to the specified decimal power. - /// - /// A decimal exponent. - /// The value of e raised to the specified power as a decimal. - public static decimal Exp(this decimal value) - { - return (decimal)Math.Exp((double)value); - } + return [low, high]; + } - /// - /// Returns the IEEE remainder of two double values. - /// - /// The dividend. - /// The divisor. - /// The remainder after division. - public static double Remainder(this double x, double y) - { - return Math.IEEERemainder(x, y); - } + /// + /// Splits a double value into its integer and fractional parts. + /// + /// The double value. + /// An array with the integer part and the fractional remainder. + public static double[] GetParts(this double value) + { + var floor = value.Floor(); + return [floor, value - floor]; + } - /// - /// Returns the IEEE remainder of two decimal values. - /// - /// The dividend. - /// The divisor. - /// The remainder after division as a decimal. - public static decimal Remainder(this decimal x, decimal y) - { - return (decimal)Math.IEEERemainder((double)x, (double)y); - } + /// + /// Splits a float value into its integer and fractional parts. + /// + /// The float value. + /// An array with the integer part and the fractional remainder. + public static float[] GetParts(this float value) + { + var floor = value.Floor(); + return [floor, value - floor]; + } - /// - /// Returns the logarithm of a double value in the specified base. - /// - /// The double value. - /// The base of the logarithm. - /// The logarithm of value in the specified base. - public static double Log(this double value, double newBase) - { - return Math.Log(value, newBase); - } + /// + /// Gets the state of the bit at the specified index in an integer. + /// + /// The integer value. + /// The bit index (0-based). + /// True if the bit is set; otherwise, false. + public static bool GetBit(this int value, int index) + { + if (index < 0 || index > 32) + throw new ArgumentOutOfRangeException(nameof(index)); - /// - /// Returns the logarithm of a decimal value in the specified base. - /// - /// The decimal value. - /// The base of the logarithm. - /// The logarithm of the value in the specified base as a decimal. - public static decimal Log(this decimal value, decimal newBase) - { - return (decimal)Math.Log((double)value, (double)newBase); - } + return (value & (1 << index)) != 0; + } - /// - /// Returns the natural logarithm of a double value. - /// - /// The double value. - /// The natural logarithm of the value. - public static double Log(this double value) - { - return Math.Log(value); - } + /// + /// Sets or clears the bit at the specified index in an integer. + /// + /// The integer value. + /// The bit index (0-based). + /// True to set the bit; false to clear it. + /// The resulting integer value after modification. + public static int SetBit(this int value, int index, bool bit) + { + if (index < 0 || index > 32) + throw new ArgumentOutOfRangeException(nameof(index)); - /// - /// Returns the natural logarithm of a decimal value. - /// - /// The decimal value. - /// The natural logarithm of the value as a decimal. - public static decimal Log(this decimal value) - { - return (decimal)Math.Log((double)value); - } + if (bit) + value |= (1 << index); + else + value &= ~(1 << index); - /// - /// Returns the base 10 logarithm of a double value. - /// - /// The double value. - /// The base 10 logarithm of the value. - public static double Log10(this double value) - { - return Math.Log10(value); - } + return value; + } - /// - /// Returns the base 10 logarithm of a decimal value. - /// - /// The decimal value. - /// The base 10 logarithm of the value as a decimal. - public static decimal Log10(this decimal value) - { - return (decimal)Math.Log10((double)value); - } + /// + /// Gets the state of the bit at the specified index in a long value. + /// + /// The long value. + /// The bit index (0-based). + /// True if the bit is set; otherwise, false. + public static bool GetBit(this long value, int index) + { + if (index < 0 || index > 64) + throw new ArgumentOutOfRangeException(nameof(index)); - /// - /// Returns the sign of a short value. - /// - /// The short value. - /// -1, 0, or 1 depending on the sign of the value. - public static int Sign(this short value) - { - return Math.Sign(value); - } + return (value & (1 << index)) != 0; + } - /// - /// Returns the sign of an int value. - /// - /// The int value. - /// -1, 0, or 1 depending on the sign of the value. - public static int Sign(this int value) - { - return Math.Sign(value); - } + /// + /// Sets or clears the bit at the specified index in a long value. + /// + /// The long value. + /// The bit index (0-based). + /// True to set the bit; false to clear it. + /// The resulting long value after modification. + public static long SetBit(this long value, int index, bool bit) + { + if (index < 0 || index > 64) + throw new ArgumentOutOfRangeException(nameof(index)); - /// - /// Returns the sign of a long value. - /// - /// The long value. - /// -1, 0, or 1 depending on the sign of the value. - public static int Sign(this long value) - { - return Math.Sign(value); - } + if (bit) + value |= (1L << index); + else + value &= ~(1L << index); - /// - /// Returns the sign of a sbyte value. - /// - /// The sbyte value. - /// -1, 0, or 1 depending on the sign of the value. - [CLSCompliant(false)] - public static int Sign(this sbyte value) - { - return Math.Sign(value); - } + return value; + } - /// - /// Returns the sign of a float value. - /// - /// The float value. - /// -1, 0, or 1 depending on the sign of the value. - public static int Sign(this float value) - { - return Math.Sign(value); - } + /// + /// Determines whether the bit at the specified 1-based index in the byte is set. + /// + /// The byte value. + /// The 1-based index of the bit to test. + /// true if the specified bit is set; otherwise, false. + public static bool GetBit(this byte value, int index) + { + return (value & (1 << index - 1)) != 0; + } - /// - /// Returns the sign of a double value. - /// - /// The double value. - /// -1, 0, or 1 depending on the sign of the value. - public static int Sign(this double value) - { - return Math.Sign(value); - } + /// + /// Sets or clears the bit at the specified index in the byte. + /// + /// The byte value. + /// The zero-based index of the bit to modify. + /// If set to true, the bit is set; otherwise, it is cleared. + /// The modified byte value. + public static byte SetBit(this byte value, int index, bool bit) + { + if (bit) + value |= (byte)(1 << index); //set bit index 1 + else + value &= (byte)~(1 << index); //set bit index 0 - /// - /// Returns the sign of a decimal value. - /// - /// The decimal value. - /// -1, 0, or 1 depending on the sign of the value. - public static int Sign(this decimal value) - { - return Math.Sign(value); - } + return value; + } - /// - /// Returns the sign of a TimeSpan value. - /// - /// The TimeSpan value. - /// -1, 0, or 1 depending on the sign of the Ticks. - public static int Sign(this TimeSpan value) - { - return value.Ticks.Sign(); - } + /// + /// Determines whether the specified bits are set in the integer value. + /// + /// The integer value. + /// The bits to test. + /// true if all specified bits are set; otherwise, false. + public static bool HasBits(this int value, int part) + { + return (value & part) == part; + } - /// - /// Returns the largest integer less than or equal to the float value. - /// - /// The float value. - /// The floor integer. - public static int Floor(this float value) - { - return (int)Math.Floor(value); - } + /// + /// Determines whether the specified bits are set in the long integer value. + /// + /// The long integer value. + /// The bits to test. + /// true if all specified bits are set; otherwise, false. + public static bool HasBits(this long value, long part) + { + return (value & part) == part; + } - /// - /// Returns the smallest integer greater than or equal to the float value. - /// - /// The float value. - /// The ceiling integer. - public static int Ceiling(this float value) - { - return (int)Math.Ceiling(value); - } + // http://stackoverflow.com/questions/389993/extracting-mantissa-and-exponent-from-double-in-c - /// - /// Extracts the high and low parts of a long value. - /// - /// The long value. - /// An array with two integers: the low and high parts. - public static int[] GetParts(this long value) - { - var high = (int)((value >> 32) & 0xFFFFFFFF); - var low = (int)(value & 0xFFFFFFFF); + /// + /// Extracts the normalized mantissa and exponent from the double value. + /// + /// The double value. + /// The extracted mantissa. + /// The extracted exponent. + public static void ExtractMantissaExponent(this double value, out long mantissa, out int exponent) + { + // Translate the double into sign, exponent and mantissa. + var bits = value.AsRaw(); - return [low, high]; - } + // Note that the shift is sign-extended, hence the test against -1 not 1 + var negative = (bits & (1L << 63)) != 0; - /// - /// Splits a double value into its integer and fractional parts. - /// - /// The double value. - /// An array with the integer part and the fractional remainder. - public static double[] GetParts(this double value) - { - var floor = value.Floor(); - return [floor, value - floor]; - } + exponent = (int)((bits >> 52) & 0x7ffL); + mantissa = bits & 0xfffffffffffffL; - /// - /// Splits a float value into its integer and fractional parts. - /// - /// The float value. - /// An array with the integer part and the fractional remainder. - public static float[] GetParts(this float value) + // Subnormal numbers; exponent is effectively one higher, + // but there's no extra normalisation bit in the mantissa + if (exponent == 0) { - var floor = value.Floor(); - return [floor, value - floor]; + exponent++; } - - /// - /// Gets the state of the bit at the specified index in an integer. - /// - /// The integer value. - /// The bit index (0-based). - /// True if the bit is set; otherwise, false. - public static bool GetBit(this int value, int index) + // Normal numbers; leave exponent as it is but add extra + // bit to the front of the mantissa + else { - if (index < 0 || index > 32) - throw new ArgumentOutOfRangeException(nameof(index)); - - return (value & (1 << index)) != 0; + mantissa = mantissa | (1L << 52); } - /// - /// Sets or clears the bit at the specified index in an integer. - /// - /// The integer value. - /// The bit index (0-based). - /// True to set the bit; false to clear it. - /// The resulting integer value after modification. - public static int SetBit(this int value, int index, bool bit) + // Bias the exponent. It's actually biased by 1023, but we're + // treating the mantissa as m.0 rather than 0.m, so we need + // to subtract another 52 from it. + exponent -= 1075; + + if (mantissa == 0) { - if (index < 0 || index > 32) - throw new ArgumentOutOfRangeException(nameof(index)); + if (negative) + mantissa = -0; - if (bit) - value |= (1 << index); - else - value &= ~(1 << index); + exponent = 0; + return; + } - return value; + /* Normalize */ + while ((mantissa & 1) == 0) + { /* i.e., Mantissa is even */ + mantissa >>= 1; + exponent++; } + } - /// - /// Gets the state of the bit at the specified index in a long value. - /// - /// The long value. - /// The bit index (0-based). - /// True if the bit is set; otherwise, false. - public static bool GetBit(this long value, int index) - { - if (index < 0 || index > 64) - throw new ArgumentOutOfRangeException(nameof(index)); + /// + /// Extracts the mantissa and scale (exponent) from the decimal value. + /// + /// The decimal value. + /// The extracted mantissa. + /// The extracted scale factor. + public static void ExtractMantissaExponent(this decimal value, out long mantissa, out int exponent) + { + var info = value.GetDecimalInfo(); + mantissa = info.Mantissa; + exponent = info.Scale; + } - return (value & (1 << index)) != 0; - } + // http://www.java-forums.org/advanced-java/4130-rounding-double-two-decimal-places.html - /// - /// Sets or clears the bit at the specified index in a long value. - /// - /// The long value. - /// The bit index (0-based). - /// True to set the bit; false to clear it. - /// The resulting long value after modification. - public static long SetBit(this long value, int index, bool bit) - { - if (index < 0 || index > 64) - throw new ArgumentOutOfRangeException(nameof(index)); + /// + /// Rounds the double value to two decimal places. + /// + /// The double value. + /// The rounded value. + public static double RoundToNearest(this double value) + { + // TODO: This method rounds to two decimals. + const double coef = 100; + var result = value * coef; + result = result.Round(); + result = result / coef; + return result; + } - if (bit) - value |= (1L << index); - else - value &= ~(1L << index); + //public static int GetDecimals(this double value) + //{ + // return ((decimal)value).GetDecimals(); + //} - return value; - } + //public static int GetDecimals(this decimal value) + //{ + // // see SqlDecimal.Scale; + // return (decimal.GetBits(value)[3] & 0x00FF0000) >> 16; + //} + + /// + /// Removes insignificant trailing zeros from the decimal value. + /// + /// The decimal value. + /// The decimal value without trailing zeros. + public static decimal RemoveTrailingZeros(this decimal value) + { + return value / 1.000000000000000000000000000000000m; + } + /// + /// Represents detailed information about a decimal value. + /// + public struct DecimalInfo + { /// - /// Determines whether the bit at the specified 1-based index in the byte is set. + /// The underlying mantissa of the decimal. /// - /// The byte value. - /// The 1-based index of the bit to test. - /// true if the specified bit is set; otherwise, false. - public static bool GetBit(this byte value, int index) - { - return (value & (1 << index - 1)) != 0; - } - + public long Mantissa; /// - /// Sets or clears the bit at the specified index in the byte. + /// The total number of digits in the decimal. /// - /// The byte value. - /// The zero-based index of the bit to modify. - /// If set to true, the bit is set; otherwise, it is cleared. - /// The modified byte value. - public static byte SetBit(this byte value, int index, bool bit) - { - if (bit) - value |= (byte)(1 << index); //set bit index 1 - else - value &= (byte)~(1 << index); //set bit index 0 - - return value; - } - + public int Precision; /// - /// Determines whether the specified bits are set in the integer value. + /// The scale (number of digits after the decimal point) of the decimal. /// - /// The integer value. - /// The bits to test. - /// true if all specified bits are set; otherwise, false. - public static bool HasBits(this int value, int part) - { - return (value & part) == part; - } - + public int Scale; /// - /// Determines whether the specified bits are set in the long integer value. + /// The count of trailing zeros in the decimal. /// - /// The long integer value. - /// The bits to test. - /// true if all specified bits are set; otherwise, false. - public static bool HasBits(this long value, long part) - { - return (value & part) == part; - } - - // http://stackoverflow.com/questions/389993/extracting-mantissa-and-exponent-from-double-in-c + public int TrailingZeros; /// - /// Extracts the normalized mantissa and exponent from the double value. + /// Gets the effective scale (scale minus trailing zeros) of the decimal. /// - /// The double value. - /// The extracted mantissa. - /// The extracted exponent. - public static void ExtractMantissaExponent(this double value, out long mantissa, out int exponent) - { - // Translate the double into sign, exponent and mantissa. - var bits = value.AsRaw(); - - // Note that the shift is sign-extended, hence the test against -1 not 1 - var negative = (bits & (1L << 63)) != 0; - - exponent = (int)((bits >> 52) & 0x7ffL); - mantissa = bits & 0xfffffffffffffL; + public int EffectiveScale => Scale - TrailingZeros; + } - // Subnormal numbers; exponent is effectively one higher, - // but there's no extra normalisation bit in the mantissa - if (exponent == 0) - { - exponent++; - } - // Normal numbers; leave exponent as it is but add extra - // bit to the front of the mantissa - else - { - mantissa = mantissa | (1L << 52); - } + // http://stackoverflow.com/questions/763942/calculate-system-decimal-precision-and-scale - // Bias the exponent. It's actually biased by 1023, but we're - // treating the mantissa as m.0 rather than 0.m, so we need - // to subtract another 52 from it. - exponent -= 1075; + /// + /// Retrieves detailed information about the decimal value, including mantissa, precision, scale, and trailing zeros. + /// + /// The decimal value. + /// A structure containing detailed decimal information. + public static DecimalInfo GetDecimalInfo(this decimal value) + { + // We want the integer parts as uint + // C# doesn't permit int[] to uint[] conversion, + // but .NET does. This is somewhat evil... + var bits = (uint[])(object)decimal.GetBits(value); - if (mantissa == 0) - { - if (negative) - mantissa = -0; + var mantissa = + (bits[2] * 4294967296m * 4294967296m) + + (bits[1] * 4294967296m) + + bits[0]; - exponent = 0; - return; - } + var scale = (bits[3] >> 16) & 31; - /* Normalize */ - while ((mantissa & 1) == 0) - { /* i.e., Mantissa is even */ - mantissa >>= 1; - exponent++; + // Precision: number of times we can divide + // by 10 before we get to 0 + var precision = 0; + if (value != 0m) + { + for (var tmp = mantissa; tmp >= 1; tmp /= 10) + { + precision++; } } - - /// - /// Extracts the mantissa and scale (exponent) from the decimal value. - /// - /// The decimal value. - /// The extracted mantissa. - /// The extracted scale factor. - public static void ExtractMantissaExponent(this decimal value, out long mantissa, out int exponent) + else { - var info = value.GetDecimalInfo(); - mantissa = info.Mantissa; - exponent = info.Scale; + // Handle zero differently. It's odd. + precision = (int)scale + 1; } - // http://www.java-forums.org/advanced-java/4130-rounding-double-two-decimal-places.html - - /// - /// Rounds the double value to two decimal places. - /// - /// The double value. - /// The rounded value. - public static double RoundToNearest(this double value) + int trailingZeros = 0; + for (var tmp = mantissa; tmp % 10m == 0 && trailingZeros < scale; tmp /= 10) { - // TODO: This method rounds to two decimals. - const double coef = 100; - var result = value * coef; - result = result.Round(); - result = result / coef; - return result; + trailingZeros++; } - //public static int GetDecimals(this double value) - //{ - // return ((decimal)value).GetDecimals(); - //} + return new DecimalInfo + { + Mantissa = (long)mantissa, + Precision = precision, + TrailingZeros = trailingZeros, + Scale = (int)scale, + }; + } - //public static int GetDecimals(this decimal value) - //{ - // // see SqlDecimal.Scale; - // return (decimal.GetBits(value)[3] & 0x00FF0000) >> 16; - //} + private static readonly SyncObject _syncObject = new(); + private static readonly Dictionary _decimalsCache = []; - /// - /// Removes insignificant trailing zeros from the decimal value. - /// - /// The decimal value. - /// The decimal value without trailing zeros. - public static decimal RemoveTrailingZeros(this decimal value) - { - return value / 1.000000000000000000000000000000000m; - } + /// + /// Gets the effective scale (number of significant decimal places) of the decimal value, using caching for performance. + /// + /// The decimal value. + /// The effective scale of the decimal. + public static int GetCachedDecimals(this decimal value) + { + int decimals; - /// - /// Represents detailed information about a decimal value. - /// - public struct DecimalInfo + lock (_syncObject) { - /// - /// The underlying mantissa of the decimal. - /// - public long Mantissa; - /// - /// The total number of digits in the decimal. - /// - public int Precision; - /// - /// The scale (number of digits after the decimal point) of the decimal. - /// - public int Scale; - /// - /// The count of trailing zeros in the decimal. - /// - public int TrailingZeros; - - /// - /// Gets the effective scale (scale minus trailing zeros) of the decimal. - /// - public int EffectiveScale => Scale - TrailingZeros; + if (_decimalsCache.TryGetValue(value, out decimals)) + return decimals; } - // http://stackoverflow.com/questions/763942/calculate-system-decimal-precision-and-scale + decimals = value.GetDecimalInfo().EffectiveScale; - /// - /// Retrieves detailed information about the decimal value, including mantissa, precision, scale, and trailing zeros. - /// - /// The decimal value. - /// A structure containing detailed decimal information. - public static DecimalInfo GetDecimalInfo(this decimal value) + lock (_syncObject) { - // We want the integer parts as uint - // C# doesn't permit int[] to uint[] conversion, - // but .NET does. This is somewhat evil... - var bits = (uint[])(object)decimal.GetBits(value); - - var mantissa = - (bits[2] * 4294967296m * 4294967296m) + - (bits[1] * 4294967296m) + - bits[0]; - - var scale = (bits[3] >> 16) & 31; - - // Precision: number of times we can divide - // by 10 before we get to 0 - var precision = 0; - if (value != 0m) - { - for (var tmp = mantissa; tmp >= 1; tmp /= 10) - { - precision++; - } - } - else + if (!_decimalsCache.ContainsKey(value)) { - // Handle zero differently. It's odd. - precision = (int)scale + 1; - } + _decimalsCache.Add(value, decimals); - int trailingZeros = 0; - for (var tmp = mantissa; tmp % 10m == 0 && trailingZeros < scale; tmp /= 10) - { - trailingZeros++; + if (_decimalsCache.Count > 10000000) + throw new InvalidOperationException(); } - - return new DecimalInfo - { - Mantissa = (long)mantissa, - Precision = precision, - TrailingZeros = trailingZeros, - Scale = (int)scale, - }; } - private static readonly SyncObject _syncObject = new(); - private static readonly Dictionary _decimalsCache = []; - - /// - /// Gets the effective scale (number of significant decimal places) of the decimal value, using caching for performance. - /// - /// The decimal value. - /// The effective scale of the decimal. - public static int GetCachedDecimals(this decimal value) - { - int decimals; + return decimals; + } - lock (_syncObject) - { - if (_decimalsCache.TryGetValue(value, out decimals)) - return decimals; - } + /// + /// Converts an angle in degrees to radians. + /// + /// The angle in degrees. + /// The angle in radians. + public static double ToRadians(this double angle) + { + return angle * (Math.PI / 180); + } - decimals = value.GetDecimalInfo().EffectiveScale; + /// + /// Converts an angle in radians to degrees. + /// + /// The angle in radians. + /// The angle in degrees. + public static double ToAngles(this double radian) + { + return radian / (Math.PI / 180); + } - lock (_syncObject) - { - if (!_decimalsCache.ContainsKey(value)) - { - _decimalsCache.Add(value, decimals); + /// + /// Calculates the real roots of a quadratic equation given coefficients a, b, and c. + /// + /// Coefficient a. + /// Coefficient b. + /// Coefficient c. + /// + /// An array containing the two roots if the discriminant is non-negative; otherwise, an empty array. + /// + public static double[] GetRoots(double a, double b, double c) + { + var sqrt = (b * b - 4 * a * c).Sqrt(); + if (sqrt >= 0) + { + var divisor = 2 * a; - if (_decimalsCache.Count > 10000000) - throw new InvalidOperationException(); - } - } + var x1 = (-b + sqrt) / divisor; + var x2 = (-b - sqrt) / divisor; - return decimals; + return [x1, x2]; } + else + return []; + } - /// - /// Converts an angle in degrees to radians. - /// - /// The angle in degrees. - /// The angle in radians. - public static double ToRadians(this double angle) - { - return angle * (Math.PI / 180); - } + /// + /// Gets the raw 64-bit integer representation of the double value. + /// + /// The double value. + /// The 64-bit integer representation. + public static long AsRaw(this double value) + { + return BitConverter.DoubleToInt64Bits(value); + } - /// - /// Converts an angle in radians to degrees. - /// - /// The angle in radians. - /// The angle in degrees. - public static double ToAngles(this double radian) - { - return radian / (Math.PI / 180); - } + /// + /// Converts a 64-bit integer representation to a double value. + /// + /// The 64-bit integer representation. + /// The double value. + public static double AsRaw(this long value) + { + return BitConverter.Int64BitsToDouble(value); + } - /// - /// Calculates the real roots of a quadratic equation given coefficients a, b, and c. - /// - /// Coefficient a. - /// Coefficient b. - /// Coefficient c. - /// - /// An array containing the two roots if the discriminant is non-negative; otherwise, an empty array. - /// - public static double[] GetRoots(double a, double b, double c) - { - var sqrt = (b * b - 4 * a * c).Sqrt(); - if (sqrt >= 0) - { - var divisor = 2 * a; + // http://nerdboys.com/2009/12/17/an-implementation-of-bitconverter-singletoint32bits/ - var x1 = (-b + sqrt) / divisor; - var x2 = (-b - sqrt) / divisor; + /// + /// Converts an integer value to a single-precision float via a byte array conversion. + /// + /// The integer value. + /// The float value. + public static float AsRaw(this int value) + { + return value.To().To(); + } - return [x1, x2]; - } - else - return []; - } + /// + /// Converts a float value to its raw 32-bit integer representation. + /// + /// The float value. + /// The 32-bit integer representation. + public static int AsRaw(this float value) + { + return value.To().To(); + } - /// - /// Gets the raw 64-bit integer representation of the double value. - /// - /// The double value. - /// The 64-bit integer representation. - public static long AsRaw(this double value) - { - return BitConverter.DoubleToInt64Bits(value); - } + /// + /// Determines if the double value is NaN (Not a Number). + /// + /// The double value. + /// true if the value is NaN; otherwise, false. + public static bool IsNaN(this double value) + { + return double.IsNaN(value); + } - /// - /// Converts a 64-bit integer representation to a double value. - /// - /// The 64-bit integer representation. - /// The double value. - public static double AsRaw(this long value) - { - return BitConverter.Int64BitsToDouble(value); - } + /// + /// Determines if the double value represents infinity. + /// + /// The double value. + /// true if the value is infinity; otherwise, false. + public static bool IsInfinity(this double value) + { + return double.IsInfinity(value); + } - // http://nerdboys.com/2009/12/17/an-implementation-of-bitconverter-singletoint32bits/ + /// + /// Determines if the double value represents negative infinity. + /// + /// The double value. + /// true if the value is negative infinity; otherwise, false. + public static bool IsNegativeInfinity(this double value) + { + return double.IsNegativeInfinity(value); + } - /// - /// Converts an integer value to a single-precision float via a byte array conversion. - /// - /// The integer value. - /// The float value. - public static float AsRaw(this int value) - { - return value.To().To(); - } + /// + /// Determines if the double value represents positive infinity. + /// + /// The double value. + /// true if the value is positive infinity; otherwise, false. + public static bool IsPositiveInfinity(this double value) + { + return double.IsPositiveInfinity(value); + } - /// - /// Converts a float value to its raw 32-bit integer representation. - /// - /// The float value. - /// The 32-bit integer representation. - public static int AsRaw(this float value) - { - return value.To().To(); - } + /// + /// Determines if the float value is NaN (Not a Number). + /// + /// The float value. + /// true if the value is NaN; otherwise, false. + public static bool IsNaN(this float value) + { + return double.IsNaN(value); + } - /// - /// Determines if the double value is NaN (Not a Number). - /// - /// The double value. - /// true if the value is NaN; otherwise, false. - public static bool IsNaN(this double value) - { - return double.IsNaN(value); - } + /// + /// Determines if the float value represents infinity. + /// + /// The float value. + /// true if the value is infinity; otherwise, false. + public static bool IsInfinity(this float value) + { + return double.IsInfinity(value); + } - /// - /// Determines if the double value represents infinity. - /// - /// The double value. - /// true if the value is infinity; otherwise, false. - public static bool IsInfinity(this double value) - { - return double.IsInfinity(value); - } + /// + /// Determines if the float value represents negative infinity. + /// + /// The float value. + /// true if the value is negative infinity; otherwise, false. + public static bool IsNegativeInfinity(this float value) + { + return double.IsNegativeInfinity(value); + } - /// - /// Determines if the double value represents negative infinity. - /// - /// The double value. - /// true if the value is negative infinity; otherwise, false. - public static bool IsNegativeInfinity(this double value) - { - return double.IsNegativeInfinity(value); - } + /// + /// Determines if the float value represents positive infinity. + /// + /// The float value. + /// true if the value is positive infinity; otherwise, false. + public static bool IsPositiveInfinity(this float value) + { + return double.IsPositiveInfinity(value); + } - /// - /// Determines if the double value represents positive infinity. - /// - /// The double value. - /// true if the value is positive infinity; otherwise, false. - public static bool IsPositiveInfinity(this double value) - { - return double.IsPositiveInfinity(value); - } + /// + /// Calculates the middle (average) value between two short values. + /// + /// The first value. + /// The second value. + /// The middle value as a decimal. + public static decimal GetMiddle(this short from, short to) + { + return ((decimal)from).GetMiddle(to); + } - /// - /// Determines if the float value is NaN (Not a Number). - /// - /// The float value. - /// true if the value is NaN; otherwise, false. - public static bool IsNaN(this float value) - { - return double.IsNaN(value); - } + /// + /// Calculates the middle (average) value between two integer values. + /// + /// The first value. + /// The second value. + /// The middle value as a decimal. + public static decimal GetMiddle(this int from, int to) + { + return ((decimal)from).GetMiddle(to); + } - /// - /// Determines if the float value represents infinity. - /// - /// The float value. - /// true if the value is infinity; otherwise, false. - public static bool IsInfinity(this float value) - { - return double.IsInfinity(value); - } + /// + /// Calculates the middle (average) value between two long integer values. + /// + /// The first value. + /// The second value. + /// The middle value as a decimal. + public static decimal GetMiddle(this long from, long to) + { + return ((decimal)from).GetMiddle(to); + } - /// - /// Determines if the float value represents negative infinity. - /// - /// The float value. - /// true if the value is negative infinity; otherwise, false. - public static bool IsNegativeInfinity(this float value) - { - return double.IsNegativeInfinity(value); - } + /// + /// Calculates the middle (average) value between two float values. + /// + /// The first value. + /// The second value. + /// The middle value as a decimal. + public static decimal GetMiddle(this float from, float to) + { + return ((decimal)from).GetMiddle((decimal)to); + } - /// - /// Determines if the float value represents positive infinity. - /// - /// The float value. - /// true if the value is positive infinity; otherwise, false. - public static bool IsPositiveInfinity(this float value) - { - return double.IsPositiveInfinity(value); - } + /// + /// Calculates the middle (average) value between two double values. + /// + /// The first value. + /// The second value. + /// The middle value as a decimal. + public static decimal GetMiddle(this double from, double to) + { + return ((decimal)from).GetMiddle((decimal)to); + } - /// - /// Calculates the middle (average) value between two short values. - /// - /// The first value. - /// The second value. - /// The middle value as a decimal. - public static decimal GetMiddle(this short from, short to) - { - return ((decimal)from).GetMiddle(to); - } + /// + /// Calculates the middle (average) value between two decimal values. + /// + /// The first value. + /// The second value. + /// The middle value as a decimal. + public static decimal GetMiddle(this decimal from, decimal to) + { + //if (from > to) + // throw new ArgumentOutOfRangeException(nameof(from)); - /// - /// Calculates the middle (average) value between two integer values. - /// - /// The first value. - /// The second value. - /// The middle value as a decimal. - public static decimal GetMiddle(this int from, int to) - { - return ((decimal)from).GetMiddle(to); - } + return (from + to) / 2; + } - /// - /// Calculates the middle (average) value between two long integer values. - /// - /// The first value. - /// The second value. - /// The middle value as a decimal. - public static decimal GetMiddle(this long from, long to) - { - return ((decimal)from).GetMiddle(to); - } + private static readonly decimal[] _posPow10 = + [ + 1M, + 10M, + 100M, + 1000M, + 10000M, + 100000M, + 1000000M, + 10000000M, + 100000000M, + 1000000000M, + 10000000000M, + 100000000000M, + ]; + + private static readonly decimal[] _negPow10 = [.. _posPow10.Select(v => 1M / v)]; - /// - /// Calculates the middle (average) value between two float values. - /// - /// The first value. - /// The second value. - /// The middle value as a decimal. - public static decimal GetMiddle(this float from, float to) - { - return ((decimal)from).GetMiddle((decimal)to); - } + /// + /// Creates a decimal value from a given mantissa and exponent by applying power-of-ten adjustments. + /// + /// The mantissa. + /// The exponent, where positive values scale up and negative values scale down. + /// The resulting decimal value. + public static decimal ToDecimal(long mantissa, int exponent) + { + decimal result = mantissa; - /// - /// Calculates the middle (average) value between two double values. - /// - /// The first value. - /// The second value. - /// The middle value as a decimal. - public static decimal GetMiddle(this double from, double to) + if (exponent >= 0) { - return ((decimal)from).GetMiddle((decimal)to); + result *= _posPow10[exponent]; } - - /// - /// Calculates the middle (average) value between two decimal values. - /// - /// The first value. - /// The second value. - /// The middle value as a decimal. - public static decimal GetMiddle(this decimal from, decimal to) + else { - //if (from > to) - // throw new ArgumentOutOfRangeException(nameof(from)); - - return (from + to) / 2; + result *= _negPow10[-exponent]; } - private static readonly decimal[] _posPow10 = - [ - 1M, - 10M, - 100M, - 1000M, - 10000M, - 100000M, - 1000000M, - 10000000M, - 100000000M, - 1000000000M, - 10000000000M, - 100000000000M, - ]; - - private static readonly decimal[] _negPow10 = [.. _posPow10.Select(v => 1M / v)]; + return result; + } - /// - /// Creates a decimal value from a given mantissa and exponent by applying power-of-ten adjustments. - /// - /// The mantissa. - /// The exponent, where positive values scale up and negative values scale down. - /// The resulting decimal value. - public static decimal ToDecimal(long mantissa, int exponent) - { - decimal result = mantissa; + private const double _minValue = (double)decimal.MinValue; + private const double _maxValue = (double)decimal.MaxValue; - if (exponent >= 0) - { - result *= _posPow10[exponent]; - } - else - { - result *= _negPow10[-exponent]; - } + /// + /// Converts a double to a decimal if within the allowed range; otherwise, returns null. + /// + /// The double value. + /// The converted decimal value or null when conversion is not possible. + public static decimal? ToDecimal(this double value) + { + return value.IsInfinity() || value.IsNaN() || value < _minValue || value > _maxValue ? (decimal?)null : (decimal)value; + } - return result; - } + /// + /// Converts a float to a decimal if within the allowed range; otherwise, returns null. + /// + /// The float value. + /// The converted decimal value or null when conversion is not possible. + public static decimal? ToDecimal(this float value) + { + return value.IsInfinity() || value.IsNaN() || value < _minValue || value > _maxValue ? (decimal?)null : (decimal)value; + } - private const double _minValue = (double)decimal.MinValue; - private const double _maxValue = (double)decimal.MaxValue; + /// + /// Returns the number of digits in a positive integer. + /// + /// The positive integer value. + /// The digit count. + /// Thrown when is negative. + public static int GetDigitCount(this int x) + { + if (x < 0) + throw new ArgumentOutOfRangeException(nameof(x)); - /// - /// Converts a double to a decimal if within the allowed range; otherwise, returns null. - /// - /// The double value. - /// The converted decimal value or null when conversion is not possible. - public static decimal? ToDecimal(this double value) + if (x < 1000000) { - return value.IsInfinity() || value.IsNaN() || value < _minValue || value > _maxValue ? (decimal?)null : (decimal)value; - } + if (x < 10) return 1; + if (x < 100) return 2; + if (x < 1000) return 3; + if (x < 10000) return 4; + if (x < 100000) return 5; - /// - /// Converts a float to a decimal if within the allowed range; otherwise, returns null. - /// - /// The float value. - /// The converted decimal value or null when conversion is not possible. - public static decimal? ToDecimal(this float value) - { - return value.IsInfinity() || value.IsNaN() || value < _minValue || value > _maxValue ? (decimal?)null : (decimal)value; + return 6; } - /// - /// Returns the number of digits in a positive integer. - /// - /// The positive integer value. - /// The digit count. - /// Thrown when is negative. - public static int GetDigitCount(this int x) - { - if (x < 0) - throw new ArgumentOutOfRangeException(nameof(x)); - - if (x < 1000000) - { - if (x < 10) return 1; - if (x < 100) return 2; - if (x < 1000) return 3; - if (x < 10000) return 4; - if (x < 100000) return 5; - - return 6; - } + if (x < 10000000) return 7; + if (x < 100000000) return 8; + if (x < 1000000000) return 9; - if (x < 10000000) return 7; - if (x < 100000000) return 8; - if (x < 1000000000) return 9; + return 10; + } - return 10; - } + /// + /// Returns the number of digits in a positive long integer. + /// + /// The positive long integer value. + /// The digit count. + /// Thrown when is negative. + public static int GetDigitCount(this long x) + { + if (x < 0) + throw new ArgumentOutOfRangeException(nameof(x)); - /// - /// Returns the number of digits in a positive long integer. - /// - /// The positive long integer value. - /// The digit count. - /// Thrown when is negative. - public static int GetDigitCount(this long x) + if (x < 1000000) { - if (x < 0) - throw new ArgumentOutOfRangeException(nameof(x)); + if (x < 10) return 1; + if (x < 100) return 2; + if (x < 1000) return 3; + if (x < 10000) return 4; + if (x < 100000) return 5; - if (x < 1000000) - { - if (x < 10) return 1; - if (x < 100) return 2; - if (x < 1000) return 3; - if (x < 10000) return 4; - if (x < 100000) return 5; + return 6; + } - return 6; - } + if (x < 10000000) return 7; + if (x < 100000000) return 8; + if (x < 1000000000) return 9; + if (x < 10000000000) return 10; + if (x < 100000000000) return 11; + if (x < 1000000000000) return 12; + if (x < 10000000000000) return 13; + if (x < 100000000000000) return 14; + if (x < 1000000000000000) return 15; + if (x < 10000000000000000) return 16; + if (x < 100000000000000000) return 17; - if (x < 10000000) return 7; - if (x < 100000000) return 8; - if (x < 1000000000) return 9; - if (x < 10000000000) return 10; - if (x < 100000000000) return 11; - if (x < 1000000000000) return 12; - if (x < 10000000000000) return 13; - if (x < 100000000000000) return 14; - if (x < 1000000000000000) return 15; - if (x < 10000000000000000) return 16; - if (x < 100000000000000000) return 17; - - return 18; - } + return 18; } } diff --git a/Common/NtpClient.cs b/Common/NtpClient.cs index a2d156fc..426a717c 100644 --- a/Common/NtpClient.cs +++ b/Common/NtpClient.cs @@ -1,87 +1,86 @@ -namespace Ecng.Common +namespace Ecng.Common; + +using System; +using System.Net; +using System.Net.Sockets; + +/// +/// Provides functionality to retrieve time from a remote NTP server. +/// +/// The endpoint of the NTP server. +public class NtpClient(EndPoint ntpServer) { - using System; - using System.Net; - using System.Net.Sockets; + private readonly EndPoint _ntpServer = ntpServer ?? throw new ArgumentNullException(nameof(ntpServer)); /// - /// Provides functionality to retrieve time from a remote NTP server. + /// Initializes a new instance of the class using the NTP server address. /// - /// The endpoint of the NTP server. - public class NtpClient(EndPoint ntpServer) + /// The NTP server address in the format "hostname:port". Default is "time-a.nist.gov:123". + public NtpClient(string ntpServer = "time-a.nist.gov:123") + : this(ntpServer.To()) { - private readonly EndPoint _ntpServer = ntpServer ?? throw new ArgumentNullException(nameof(ntpServer)); - - /// - /// Initializes a new instance of the class using the NTP server address. - /// - /// The NTP server address in the format "hostname:port". Default is "time-a.nist.gov:123". - public NtpClient(string ntpServer = "time-a.nist.gov:123") - : this(ntpServer.To()) - { - //var address = Dns.GetHostEntry(ntpServer).AddressList; - - //if (address is null || address.Length == 0) - // throw new ArgumentException(string.Format("Could not resolve ip address from '{0}'.", ntpServer), "ntpServer"); - - //_endPoint = new IPEndPoint(address[0], 123); - } - - /// - /// Retrieves the local time based on the specified time zone. - /// - /// The time zone information. - /// The timeout in milliseconds for the NTP request. Default is 5000ms. - /// The local time adjusted to the specified time zone. - /// Thrown when is null. - public DateTime GetLocalTime(TimeZoneInfo info, int timeout = 5000) - { - if (info is null) - throw new ArgumentNullException(nameof(info)); - - var utcTime = GetUtcTime(timeout); - return utcTime + info.GetUtcOffset(utcTime); - } - - /// - /// Retrieves the UTC time from the NTP server. - /// - /// The timeout in milliseconds for the NTP request. Default is 5000ms. - /// The UTC time as provided by the NTP server. - public DateTime GetUtcTime(int timeout = 5000) - { - using var s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); - s.SendTimeout = timeout; - s.ReceiveTimeout = timeout; - - s.Connect(_ntpServer); - - var ntpData = new byte[48]; // RFC 2030 - ntpData[0] = 0x1B; - for (var i = 1; i < 48; i++) - ntpData[i] = 0; - - s.Send(ntpData); - s.Receive(ntpData); - - const byte offsetTransmitTime = 40; - ulong intpart = 0; - ulong fractpart = 0; - - for (var i = 0; i <= 3; i++) - intpart = 256 * intpart + ntpData[offsetTransmitTime + i]; - - for (var i = 4; i <= 7; i++) - fractpart = 256 * fractpart + ntpData[offsetTransmitTime + i]; - - var milliseconds = (intpart * 1000 + (fractpart * 1000) / 0x100000000L); - - var timeSpan = TimeSpan.FromMilliseconds(milliseconds); - - var dateTime = new DateTime(1900, 1, 1); - dateTime += timeSpan; - - return dateTime; - } + //var address = Dns.GetHostEntry(ntpServer).AddressList; + + //if (address is null || address.Length == 0) + // throw new ArgumentException(string.Format("Could not resolve ip address from '{0}'.", ntpServer), "ntpServer"); + + //_endPoint = new IPEndPoint(address[0], 123); + } + + /// + /// Retrieves the local time based on the specified time zone. + /// + /// The time zone information. + /// The timeout in milliseconds for the NTP request. Default is 5000ms. + /// The local time adjusted to the specified time zone. + /// Thrown when is null. + public DateTime GetLocalTime(TimeZoneInfo info, int timeout = 5000) + { + if (info is null) + throw new ArgumentNullException(nameof(info)); + + var utcTime = GetUtcTime(timeout); + return utcTime + info.GetUtcOffset(utcTime); + } + + /// + /// Retrieves the UTC time from the NTP server. + /// + /// The timeout in milliseconds for the NTP request. Default is 5000ms. + /// The UTC time as provided by the NTP server. + public DateTime GetUtcTime(int timeout = 5000) + { + using var s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + s.SendTimeout = timeout; + s.ReceiveTimeout = timeout; + + s.Connect(_ntpServer); + + var ntpData = new byte[48]; // RFC 2030 + ntpData[0] = 0x1B; + for (var i = 1; i < 48; i++) + ntpData[i] = 0; + + s.Send(ntpData); + s.Receive(ntpData); + + const byte offsetTransmitTime = 40; + ulong intpart = 0; + ulong fractpart = 0; + + for (var i = 0; i <= 3; i++) + intpart = 256 * intpart + ntpData[offsetTransmitTime + i]; + + for (var i = 4; i <= 7; i++) + fractpart = 256 * fractpart + ntpData[offsetTransmitTime + i]; + + var milliseconds = (intpart * 1000 + (fractpart * 1000) / 0x100000000L); + + var timeSpan = TimeSpan.FromMilliseconds(milliseconds); + + var dateTime = new DateTime(1900, 1, 1); + dateTime += timeSpan; + + return dateTime; } } \ No newline at end of file diff --git a/Common/NullableEx.cs b/Common/NullableEx.cs index 716df7fe..230c085c 100644 --- a/Common/NullableEx.cs +++ b/Common/NullableEx.cs @@ -1,80 +1,79 @@ -namespace Ecng.Common +namespace Ecng.Common; + +using System; +using System.Runtime.Serialization; + +/// +/// Represents a wrapper for nullable types that can be serialized. +/// +/// The underlying type of the nullable value. +[Serializable] +[DataContract] +public class NullableEx : Equatable> { - using System; - using System.Runtime.Serialization; + #region HasValue /// - /// Represents a wrapper for nullable types that can be serialized. + /// Gets a value indicating whether the current NullableEx object has a valid value. /// - /// The underlying type of the nullable value. - [Serializable] - [DataContract] - public class NullableEx : Equatable> - { - #region HasValue + public bool HasValue { get; private set; } - /// - /// Gets a value indicating whether the current NullableEx object has a valid value. - /// - public bool HasValue { get; private set; } + #endregion - #endregion + #region Value - #region Value + private T _value; - private T _value; - - /// - /// Gets or sets the value of the current NullableEx object. - /// - /// The HasValue property is false. - [DataMember] - public T Value + /// + /// Gets or sets the value of the current NullableEx object. + /// + /// The HasValue property is false. + [DataMember] + public T Value + { + get + { + if (HasValue) + return _value; + else + throw new InvalidOperationException(); + } + set { - get - { - if (HasValue) - return _value; - else - throw new InvalidOperationException(); - } - set - { - _value = value; - HasValue = true; - } + _value = value; + HasValue = true; } + } - #endregion + #endregion - #region Equatable> Members + #region Equatable> Members - /// - protected override bool OnEquals(NullableEx other) - { - if (HasValue != other.HasValue) - return false; + /// + protected override bool OnEquals(NullableEx other) + { + if (HasValue != other.HasValue) + return false; - return !HasValue || Value.Equals(other.Value); - } + return !HasValue || Value.Equals(other.Value); + } - /// - public override NullableEx Clone() - { - var retVal = new NullableEx(); + /// + public override NullableEx Clone() + { + var retVal = new NullableEx(); - if (HasValue) - retVal.Value = Value; + if (HasValue) + retVal.Value = Value; - return retVal; - } + return retVal; + } - #endregion + #endregion - /// - public override int GetHashCode() - { - return HasValue ? Value.GetHashCode() : typeof(T).GetHashCode(); - } + /// + public override int GetHashCode() + { + return HasValue ? Value.GetHashCode() : typeof(T).GetHashCode(); } } \ No newline at end of file diff --git a/Common/NullableHelper.cs b/Common/NullableHelper.cs index 55caa9fb..40ea82bf 100644 --- a/Common/NullableHelper.cs +++ b/Common/NullableHelper.cs @@ -1,136 +1,135 @@ -namespace Ecng.Common +namespace Ecng.Common; + +using System; + +/// +/// Provides helper extension methods for working with nullable types. +/// +public static class NullableHelper { - using System; + /// + /// Gets the underlying type argument of the specified nullable type. + /// + /// The nullable type to get the underlying type from. + /// The underlying type if the provided type is nullable; otherwise, null. + public static Type GetUnderlyingType(this Type nullableType) + { + return Nullable.GetUnderlyingType(nullableType); + } + + /// + /// Determines whether the specified type is a nullable type. + /// + /// The type to inspect. + /// True if the type is nullable; otherwise, false. + /// Thrown when is null. + public static bool IsNullable(this Type type) + { + if (type is null) + throw new ArgumentNullException(nameof(type)); + + return type.GetUnderlyingType() != null; + } + + /// + /// Determines whether the specified value is null. + /// For value types, this method returns false. + /// + /// The type of the value. + /// The value to check for null. + /// True if the value is null; otherwise, false. + public static bool IsNull(this T value) + { + return value.IsNull(false); + } + + /// + /// Determines whether the specified value is null or its default for value types. + /// + /// The type of the value. + /// The value to check. + /// + /// If true and the type is a value type, the value is compared to its default value. + /// + /// + /// True if the reference type value is null or if the value type is equal to its default value when is true; otherwise, false. + /// + public static bool IsNull(this T value, bool checkValueTypeOnDefault) + { + if (value is not ValueType) + return value is null; + + if (!checkValueTypeOnDefault) + return false; + + var defValue = default(T); + + // typeof(T) == typeof(object) + defValue ??= (T)Activator.CreateInstance(value.GetType()); + + return value.Equals(defValue); + } /// - /// Provides helper extension methods for working with nullable types. + /// Converts the value to a result type using the appropriate conversion function based on whether the value is null. /// - public static class NullableHelper + /// The type of the input value. Must be a reference type. + /// The type of the result. + /// The input value to convert. + /// A function to convert the value when it is not null. + /// A function to produce a result when the value is null. + /// + /// The result of applying either or to the value. + /// + /// + /// Thrown when or is null. + /// + public static TResult Convert(this T value, Func notNullFunc, Func nullFunc) + where T : class { - /// - /// Gets the underlying type argument of the specified nullable type. - /// - /// The nullable type to get the underlying type from. - /// The underlying type if the provided type is nullable; otherwise, null. - public static Type GetUnderlyingType(this Type nullableType) - { - return Nullable.GetUnderlyingType(nullableType); - } - - /// - /// Determines whether the specified type is a nullable type. - /// - /// The type to inspect. - /// True if the type is nullable; otherwise, false. - /// Thrown when is null. - public static bool IsNullable(this Type type) - { - if (type is null) - throw new ArgumentNullException(nameof(type)); - - return type.GetUnderlyingType() != null; - } - - /// - /// Determines whether the specified value is null. - /// For value types, this method returns false. - /// - /// The type of the value. - /// The value to check for null. - /// True if the value is null; otherwise, false. - public static bool IsNull(this T value) - { - return value.IsNull(false); - } - - /// - /// Determines whether the specified value is null or its default for value types. - /// - /// The type of the value. - /// The value to check. - /// - /// If true and the type is a value type, the value is compared to its default value. - /// - /// - /// True if the reference type value is null or if the value type is equal to its default value when is true; otherwise, false. - /// - public static bool IsNull(this T value, bool checkValueTypeOnDefault) - { - if (value is not ValueType) - return value is null; - - if (!checkValueTypeOnDefault) - return false; - - var defValue = default(T); - - // typeof(T) == typeof(object) - defValue ??= (T)Activator.CreateInstance(value.GetType()); - - return value.Equals(defValue); - } - - /// - /// Converts the value to a result type using the appropriate conversion function based on whether the value is null. - /// - /// The type of the input value. Must be a reference type. - /// The type of the result. - /// The input value to convert. - /// A function to convert the value when it is not null. - /// A function to produce a result when the value is null. - /// - /// The result of applying either or to the value. - /// - /// - /// Thrown when or is null. - /// - public static TResult Convert(this T value, Func notNullFunc, Func nullFunc) - where T : class - { - if (notNullFunc is null) - throw new ArgumentNullException(nameof(notNullFunc)); - - if (nullFunc is null) - throw new ArgumentNullException(nameof(nullFunc)); - - return value is null ? nullFunc() : notNullFunc(value); - } - - /// - /// Returns the default value as null if the value is equal to its default; otherwise, returns the value as a nullable type. - /// - /// The value type. - /// The value to check. - /// - /// Null if the value is the default of its type; otherwise, the value wrapped as a nullable type. - /// - public static T? DefaultAsNull(this T value) - where T : struct - { - return value.IsDefault() ? null : value; - } - - /// - /// Creates a nullable type from the given type. - /// - /// The type to make nullable. - /// A nullable type of the given type. - public static Type MakeNullable(this Type type) - => typeof(Nullable<>).Make(type); - - /// - /// Converts the given type to a nullable type if it is a non-nullable value type. - /// - /// The type to convert. - /// - /// The nullable version of the type if it is a value type and not already nullable; otherwise, the original type. - /// - public static Type TryMakeNullable(this Type type) - { - if (!type.IsNullable() && type.IsValueType) - type = typeof(Nullable<>).Make(type); - - return type; - } + if (notNullFunc is null) + throw new ArgumentNullException(nameof(notNullFunc)); + + if (nullFunc is null) + throw new ArgumentNullException(nameof(nullFunc)); + + return value is null ? nullFunc() : notNullFunc(value); + } + + /// + /// Returns the default value as null if the value is equal to its default; otherwise, returns the value as a nullable type. + /// + /// The value type. + /// The value to check. + /// + /// Null if the value is the default of its type; otherwise, the value wrapped as a nullable type. + /// + public static T? DefaultAsNull(this T value) + where T : struct + { + return value.IsDefault() ? null : value; + } + + /// + /// Creates a nullable type from the given type. + /// + /// The type to make nullable. + /// A nullable type of the given type. + public static Type MakeNullable(this Type type) + => typeof(Nullable<>).Make(type); + + /// + /// Converts the given type to a nullable type if it is a non-nullable value type. + /// + /// The type to convert. + /// + /// The nullable version of the type if it is a value type and not already nullable; otherwise, the original type. + /// + public static Type TryMakeNullable(this Type type) + { + if (!type.IsNullable() && type.IsValueType) + type = typeof(Nullable<>).Make(type); + + return type; } } \ No newline at end of file diff --git a/Common/OperatingSystemEx.cs b/Common/OperatingSystemEx.cs index 6e620999..946b34cf 100644 --- a/Common/OperatingSystemEx.cs +++ b/Common/OperatingSystemEx.cs @@ -1,120 +1,119 @@ -namespace Ecng.Common +namespace Ecng.Common; + +using System.Linq; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.IO; +using System; + +/// +/// Provides operating system related helper methods and properties. +/// +public static class OperatingSystemEx { - using System.Linq; - using System.Collections.Generic; - using System.Runtime.InteropServices; - using System.IO; - using System; + /// + /// Determines whether the current operating system is Windows. + /// + /// true if the operating system is Windows; otherwise, false. + public static bool IsWindows() => OSPlatform.Windows.IsOSPlatform(); + + /// + /// Determines whether the current operating system is macOS. + /// + /// true if the operating system is macOS; otherwise, false. + public static bool IsMacOS() => OSPlatform.OSX.IsOSPlatform(); + + /// + /// Determines whether the current operating system is Linux. + /// + /// true if the operating system is Linux; otherwise, false. + public static bool IsLinux() => OSPlatform.Linux.IsOSPlatform(); /// - /// Provides operating system related helper methods and properties. + /// Determines whether the specified is the current platform. /// - public static class OperatingSystemEx + /// The operating system platform to check. + /// true if the specified platform is the current operating system; otherwise, false. + public static bool IsOSPlatform(this OSPlatform platform) + => RuntimeInformation.IsOSPlatform(platform); + + /// + /// Gets all available operating system platforms defined in . + /// + public static IEnumerable Platforms => + [.. typeof(OSPlatform) + .GetProperties() + .Where(p => p.PropertyType == typeof(OSPlatform)) + .Select(p => (OSPlatform)p.GetValue(null))]; + + /// + /// Gets a value indicating whether the current runtime framework is .NET Framework. + /// + public static bool IsFramework + => RuntimeInformation.FrameworkDescription.StartsWithIgnoreCase(".NET Framework"); + + /// + /// Retrieves runtime package versions based on the provided framework version. + /// + /// The framework version to look up corresponding runtime packages. + /// + /// A dictionary containing the package name as the key and its as the value. + /// + /// Thrown when is null. + public static IDictionary GetRuntimePackages(Version fwVer) { - /// - /// Determines whether the current operating system is Windows. - /// - /// true if the operating system is Windows; otherwise, false. - public static bool IsWindows() => OSPlatform.Windows.IsOSPlatform(); - - /// - /// Determines whether the current operating system is macOS. - /// - /// true if the operating system is macOS; otherwise, false. - public static bool IsMacOS() => OSPlatform.OSX.IsOSPlatform(); - - /// - /// Determines whether the current operating system is Linux. - /// - /// true if the operating system is Linux; otherwise, false. - public static bool IsLinux() => OSPlatform.Linux.IsOSPlatform(); - - /// - /// Determines whether the specified is the current platform. - /// - /// The operating system platform to check. - /// true if the specified platform is the current operating system; otherwise, false. - public static bool IsOSPlatform(this OSPlatform platform) - => RuntimeInformation.IsOSPlatform(platform); - - /// - /// Gets all available operating system platforms defined in . - /// - public static IEnumerable Platforms => - [.. typeof(OSPlatform) - .GetProperties() - .Where(p => p.PropertyType == typeof(OSPlatform)) - .Select(p => (OSPlatform)p.GetValue(null))]; - - /// - /// Gets a value indicating whether the current runtime framework is .NET Framework. - /// - public static bool IsFramework - => RuntimeInformation.FrameworkDescription.StartsWithIgnoreCase(".NET Framework"); - - /// - /// Retrieves runtime package versions based on the provided framework version. - /// - /// The framework version to look up corresponding runtime packages. - /// - /// A dictionary containing the package name as the key and its as the value. - /// - /// Thrown when is null. - public static IDictionary GetRuntimePackages(Version fwVer) + if (fwVer is null) + throw new ArgumentNullException(nameof(fwVer)); + + var runtimePackages = new Dictionary(StringComparer.InvariantCultureIgnoreCase) { - if (fwVer is null) - throw new ArgumentNullException(nameof(fwVer)); + { "NETStandard.Library", fwVer }, + }; - var runtimePackages = new Dictionary(StringComparer.InvariantCultureIgnoreCase) - { - { "NETStandard.Library", fwVer }, - }; + try + { + var fi = new DirectoryInfo(RuntimeEnvironment.GetRuntimeDirectory()); - try + if (fi.Exists && fi.Parent?.Parent is not null) { - var fi = new DirectoryInfo(RuntimeEnvironment.GetRuntimeDirectory()); + var dirs = fi.Parent.Parent.GetDirectories(); - if (fi.Exists && fi.Parent?.Parent is not null) + // Local function to fill the runtime packages from a specific folder. + void fillPackages(string name) { - var dirs = fi.Parent.Parent.GetDirectories(); + var dir = dirs.FirstOrDefault(d => d.Name.EqualsIgnoreCase(name)); - // Local function to fill the runtime packages from a specific folder. - void fillPackages(string name) - { - var dir = dirs.FirstOrDefault(d => d.Name.EqualsIgnoreCase(name)); - - if (dir is null) - return; + if (dir is null) + return; - var verDir = dir - .GetDirectories() - .Select(d => (dir: d, ver: Version.TryParse(d.Name, out var ver) ? ver : null)) - .Where(t => t.ver is not null && t.ver.Major == fwVer.Major && t.ver.Minor == fwVer.Minor) - .OrderByDescending(t => t.ver) - .FirstOrDefault().dir; + var verDir = dir + .GetDirectories() + .Select(d => (dir: d, ver: Version.TryParse(d.Name, out var ver) ? ver : null)) + .Where(t => t.ver is not null && t.ver.Major == fwVer.Major && t.ver.Minor == fwVer.Minor) + .OrderByDescending(t => t.ver) + .FirstOrDefault().dir; - if (verDir is null || !Version.TryParse(verDir.Name, out var ver)) - return; + if (verDir is null || !Version.TryParse(verDir.Name, out var ver)) + return; - foreach (var packageName in verDir.GetFiles("*.dll").Select(f => Path.GetFileNameWithoutExtension(f.Name))) - { - if (runtimePackages.ContainsKey(packageName)) - continue; + foreach (var packageName in verDir.GetFiles("*.dll").Select(f => Path.GetFileNameWithoutExtension(f.Name))) + { + if (runtimePackages.ContainsKey(packageName)) + continue; - runtimePackages.Add(packageName, ver); - } + runtimePackages.Add(packageName, ver); } - - fillPackages("Microsoft.NETCore.App"); - fillPackages("Microsoft.WindowsDesktop.App"); } - } - catch - { - // Suppress any exceptions during package retrieval. - } - return runtimePackages; + fillPackages("Microsoft.NETCore.App"); + fillPackages("Microsoft.WindowsDesktop.App"); + } } + catch + { + // Suppress any exceptions during package retrieval. + } + + return runtimePackages; } } \ No newline at end of file diff --git a/Common/Platforms.cs b/Common/Platforms.cs index 49cd7fa9..9eac1338 100644 --- a/Common/Platforms.cs +++ b/Common/Platforms.cs @@ -1,50 +1,49 @@ -namespace Ecng.Common -{ - using System; +namespace Ecng.Common; + +using System; +/// +/// Enumeration for representing supported platform types. +/// +public enum Platforms +{ /// - /// Enumeration for representing supported platform types. + /// Represents the 32-bit platform. /// - public enum Platforms - { - /// - /// Represents the 32-bit platform. - /// - x86, + x86, - /// - /// Represents the 64-bit platform. - /// - x64, + /// + /// Represents the 64-bit platform. + /// + x64, - /// - /// Represents any CPU architecture. - /// - AnyCPU, - } + /// + /// Represents any CPU architecture. + /// + AnyCPU, +} +/// +/// Provides helper methods for . +/// +public static class PlatformHelper +{ /// - /// Provides helper methods for . + /// Determines whether the current process is compatible with the specified platform. /// - public static class PlatformHelper + /// The target platform to check compatibility for. + /// + /// true if the current process is compatible with the specified platform; otherwise, false. + /// + /// Thrown when the platform is not recognized. + public static bool IsCompatible(this Platforms platform) { - /// - /// Determines whether the current process is compatible with the specified platform. - /// - /// The target platform to check compatibility for. - /// - /// true if the current process is compatible with the specified platform; otherwise, false. - /// - /// Thrown when the platform is not recognized. - public static bool IsCompatible(this Platforms platform) + return platform switch { - return platform switch - { - Platforms.x86 => !Environment.Is64BitProcess, - Platforms.x64 => Environment.Is64BitProcess, - Platforms.AnyCPU => true, - _ => throw new ArgumentOutOfRangeException(nameof(platform)), - }; - } + Platforms.x86 => !Environment.Is64BitProcess, + Platforms.x64 => Environment.Is64BitProcess, + Platforms.AnyCPU => true, + _ => throw new ArgumentOutOfRangeException(nameof(platform)), + }; } } \ No newline at end of file diff --git a/Common/ProcessExtensions.cs b/Common/ProcessExtensions.cs index f3a7917d..213bd867 100644 --- a/Common/ProcessExtensions.cs +++ b/Common/ProcessExtensions.cs @@ -1,147 +1,146 @@ -namespace Ecng.Common -{ +namespace Ecng.Common; + #if !NET5_0_OR_GREATER - using System; - using System.Diagnostics; - using System.Threading; - using System.Threading.Tasks; +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; + +static class ProcessExtensions +{ + public static bool Associated(this Process process) + { + if (process is null) + throw new ArgumentNullException(nameof(process)); - static class ProcessExtensions + return process.Handle != default; + } + + // https://github.com/dotnet/runtime/blob/main/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.cs#L1422 + + /// + /// Instructs the Process component to wait for the associated process to exit, or + /// for the to be canceled. + /// + /// + /// A task that will complete when the process has exited, cancellation has been requested, + /// or an error occurs. + /// + public static async Task WaitForExitAsync(this Process process, CancellationToken cancellationToken = default) { - public static bool Associated(this Process process) + if (process is null) + throw new ArgumentNullException(nameof(process)); + + // Because the process has already started by the time this method is called, + // we're in a race against the process to set up our exit handlers before the process + // exits. As a result, there are several different flows that must be handled: + // + // CASE 1: WE ENABLE EVENTS + // This is the "happy path". In this case we enable events. + // + // CASE 1.1: PROCESS EXITS OR IS CANCELED AFTER REGISTERING HANDLER + // This case continues the "happy path". The process exits or waiting is canceled after + // registering the handler and no special cases are needed. + // + // CASE 1.2: PROCESS EXITS BEFORE REGISTERING HANDLER + // It's possible that the process can exit after we enable events but before we reigster + // the handler. In that case we must check for exit after registering the handler. + // + // + // CASE 2: PROCESS EXITS BEFORE ENABLING EVENTS + // The process may exit before we attempt to enable events. In that case EnableRaisingEvents + // will throw an exception like this: + // System.InvalidOperationException : Cannot process request because the process (42) has exited. + // In this case we catch the InvalidOperationException. If the process has exited, our work + // is done and we return. If for any reason (now or in the future) enabling events fails + // and the process has not exited, bubble the exception up to the user. + // + // + // CASE 3: USER ALREADY ENABLED EVENTS + // In this case the user has already enabled raising events. Re-enabling events is a no-op + // as the value hasn't changed. However, no-op also means that if the process has already + // exited, EnableRaisingEvents won't throw an exception. + // + // CASE 3.1: PROCESS EXITS OR IS CANCELED AFTER REGISTERING HANDLER + // (See CASE 1.1) + // + // CASE 3.2: PROCESS EXITS BEFORE REGISTERING HANDLER + // (See CASE 1.2) + + if (!process.Associated()) { - if (process is null) - throw new ArgumentNullException(nameof(process)); + throw new InvalidOperationException("Not associated."); + } - return process.Handle != default; + if (!process.HasExited) + { + // Early out for cancellation before doing more expensive work + cancellationToken.ThrowIfCancellationRequested(); } - // https://github.com/dotnet/runtime/blob/main/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.cs#L1422 - - /// - /// Instructs the Process component to wait for the associated process to exit, or - /// for the to be canceled. - /// - /// - /// A task that will complete when the process has exited, cancellation has been requested, - /// or an error occurs. - /// - public static async Task WaitForExitAsync(this Process process, CancellationToken cancellationToken = default) + try + { + // CASE 1: We enable events + // CASE 2: Process exits before enabling events (and throws an exception) + // CASE 3: User already enabled events (no-op) + process.EnableRaisingEvents = true; + } + catch (InvalidOperationException) { - if (process is null) - throw new ArgumentNullException(nameof(process)); - - // Because the process has already started by the time this method is called, - // we're in a race against the process to set up our exit handlers before the process - // exits. As a result, there are several different flows that must be handled: - // - // CASE 1: WE ENABLE EVENTS - // This is the "happy path". In this case we enable events. - // - // CASE 1.1: PROCESS EXITS OR IS CANCELED AFTER REGISTERING HANDLER - // This case continues the "happy path". The process exits or waiting is canceled after - // registering the handler and no special cases are needed. - // - // CASE 1.2: PROCESS EXITS BEFORE REGISTERING HANDLER - // It's possible that the process can exit after we enable events but before we reigster - // the handler. In that case we must check for exit after registering the handler. - // - // - // CASE 2: PROCESS EXITS BEFORE ENABLING EVENTS - // The process may exit before we attempt to enable events. In that case EnableRaisingEvents - // will throw an exception like this: - // System.InvalidOperationException : Cannot process request because the process (42) has exited. - // In this case we catch the InvalidOperationException. If the process has exited, our work - // is done and we return. If for any reason (now or in the future) enabling events fails - // and the process has not exited, bubble the exception up to the user. - // - // - // CASE 3: USER ALREADY ENABLED EVENTS - // In this case the user has already enabled raising events. Re-enabling events is a no-op - // as the value hasn't changed. However, no-op also means that if the process has already - // exited, EnableRaisingEvents won't throw an exception. - // - // CASE 3.1: PROCESS EXITS OR IS CANCELED AFTER REGISTERING HANDLER - // (See CASE 1.1) - // - // CASE 3.2: PROCESS EXITS BEFORE REGISTERING HANDLER - // (See CASE 1.2) - - if (!process.Associated()) + // CASE 2: If the process has exited, our work is done, otherwise bubble the + // exception up to the user + if (process.HasExited) { - throw new InvalidOperationException("Not associated."); + await WaitUntilOutputEOF(cancellationToken).ConfigureAwait(false); + return; } - if (!process.HasExited) - { - // Early out for cancellation before doing more expensive work - cancellationToken.ThrowIfCancellationRequested(); - } + throw; + } - try + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + EventHandler handler = (_, _) => tcs.TrySetResult(true); + process.Exited += handler; + + try + { + if (process.HasExited) { - // CASE 1: We enable events - // CASE 2: Process exits before enabling events (and throws an exception) - // CASE 3: User already enabled events (no-op) - process.EnableRaisingEvents = true; + // CASE 1.2 & CASE 3.2: Handle race where the process exits before registering the handler } - catch (InvalidOperationException) + else { - // CASE 2: If the process has exited, our work is done, otherwise bubble the - // exception up to the user - if (process.HasExited) + // CASE 1.1 & CASE 3.1: Process exits or is canceled here + using (cancellationToken.Register(() => tcs.TrySetCanceled(cancellationToken), false)) { - await WaitUntilOutputEOF(cancellationToken).ConfigureAwait(false); - return; + await tcs.Task.ConfigureAwait(false); } - - throw; } - var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - EventHandler handler = (_, _) => tcs.TrySetResult(true); - process.Exited += handler; - - try - { - if (process.HasExited) - { - // CASE 1.2 & CASE 3.2: Handle race where the process exits before registering the handler - } - else - { - // CASE 1.1 & CASE 3.1: Process exits or is canceled here - using (cancellationToken.Register(() => tcs.TrySetCanceled(cancellationToken), false)) - { - await tcs.Task.ConfigureAwait(false); - } - } - - // Wait until output streams have been drained - await WaitUntilOutputEOF(cancellationToken).ConfigureAwait(false); - } - finally - { - process.Exited -= handler; - } + // Wait until output streams have been drained + await WaitUntilOutputEOF(cancellationToken).ConfigureAwait(false); + } + finally + { + process.Exited -= handler; + } - static Task WaitUntilOutputEOF(CancellationToken cancellationToken) - { - // TODO - return Task.CompletedTask; - - //if (process._output is not null) - //{ - // await process._output.EOF.WaitAsync(cancellationToken).ConfigureAwait(false); - //} - - //if (process._error is not null) - //{ - // await process._error.EOF.WaitAsync(cancellationToken).ConfigureAwait(false); - //} - } + static Task WaitUntilOutputEOF(CancellationToken cancellationToken) + { + // TODO + return Task.CompletedTask; + + //if (process._output is not null) + //{ + // await process._output.EOF.WaitAsync(cancellationToken).ConfigureAwait(false); + //} + + //if (process._error is not null) + //{ + // await process._error.EOF.WaitAsync(cancellationToken).ConfigureAwait(false); + //} } } -#endif } +#endif diff --git a/Common/RandomArray.cs b/Common/RandomArray.cs index be83b047..5fb523c4 100644 --- a/Common/RandomArray.cs +++ b/Common/RandomArray.cs @@ -1,130 +1,129 @@ -namespace Ecng.Common +namespace Ecng.Common; + +using System; +using System.Linq; + +/// +/// Provides functionality to generate a random array of values of type T. +/// +/// The type of the elements in the array. Must be a value type that implements IComparable. +public class RandomArray + where T : struct, IComparable { - using System; - using System.Linq; + private readonly SyncObject _lock = new(); + private readonly T[] _data; + private int _index; /// - /// Provides functionality to generate a random array of values of type T. + /// Initializes a new instance of the class with a specified count. /// - /// The type of the elements in the array. Must be a value type that implements IComparable. - public class RandomArray - where T : struct, IComparable + /// The number of elements in the random array. + /// Thrown when type T is not supported for random generation. + public RandomArray(int count) { - private readonly SyncObject _lock = new(); - private readonly T[] _data; - private int _index; - - /// - /// Initializes a new instance of the class with a specified count. - /// - /// The number of elements in the random array. - /// Thrown when type T is not supported for random generation. - public RandomArray(int count) + Count = count; + + _data = [.. Enumerable.Repeat(default, count)]; + + if (typeof(T) == typeof(double)) + { + for (var i = 0; i < _data.Length; i++) + _data[i] = RandomGen.GetDouble().To(); + } + else if (typeof(T) == typeof(int)) + { + for (var i = 0; i < _data.Length; i++) + _data[i] = RandomGen.GetInt().To(); + } + else if (typeof(T) == typeof(bool)) + { + for (var i = 0; i < _data.Length; i++) + _data[i] = RandomGen.GetBool().To(); + } + else if (typeof(T).IsEnum) + { + for (var i = 0; i < _data.Length; i++) + _data[i] = RandomGen.GetEnum(); + } + else if (typeof(T) == typeof(byte)) { - Count = count; + RandomGen.GetBytes(count).CopyTo(_data, 0); + } + else + throw new NotSupportedException(); + } - _data = [.. Enumerable.Repeat(default, count)]; + /// + /// Initializes a new instance of the class within a specified range. + /// + /// The minimum value of the range. + /// The maximum value of the range. + /// The number of elements in the random array. + /// Thrown when is greater than . + /// Thrown when type T is not supported for ranged random generation. + public RandomArray(T min, T max, int count) + { + Min = min; + Max = max; + Count = count; - if (typeof(T) == typeof(double)) - { - for (var i = 0; i < _data.Length; i++) - _data[i] = RandomGen.GetDouble().To(); - } - else if (typeof(T) == typeof(int)) - { - for (var i = 0; i < _data.Length; i++) - _data[i] = RandomGen.GetInt().To(); - } - else if (typeof(T) == typeof(bool)) + if (min.CompareTo(max) > 0) + throw new ArgumentException("min > max"); + + _data = [.. Enumerable.Repeat(min, count)]; + + if (min.CompareTo(max) != 0) + { + if (typeof(T) == typeof(int)) { + var minInt = min.To(); + var maxInt = max.To(); + for (var i = 0; i < _data.Length; i++) - _data[i] = RandomGen.GetBool().To(); + _data[i] = RandomGen.GetInt(minInt, maxInt).To(); } - else if (typeof(T).IsEnum) + else if (typeof(T) == typeof(TimeSpan)) { + var minTimeSpan = min.To(); + var maxTimeSpan = max.To(); + for (var i = 0; i < _data.Length; i++) - _data[i] = RandomGen.GetEnum(); - } - else if (typeof(T) == typeof(byte)) - { - RandomGen.GetBytes(count).CopyTo(_data, 0); + _data[i] = RandomGen.GetTime(minTimeSpan, maxTimeSpan).To(); } else throw new NotSupportedException(); } + } - /// - /// Initializes a new instance of the class within a specified range. - /// - /// The minimum value of the range. - /// The maximum value of the range. - /// The number of elements in the random array. - /// Thrown when is greater than . - /// Thrown when type T is not supported for ranged random generation. - public RandomArray(T min, T max, int count) - { - Min = min; - Max = max; - Count = count; - - if (min.CompareTo(max) > 0) - throw new ArgumentException("min > max"); + /// + /// Gets the total number of elements in the random array. + /// + public int Count { get; } - _data = [.. Enumerable.Repeat(min, count)]; + /// + /// Gets the minimum value (or default seed value) used in the random array generation. + /// + public T Min { get; } - if (min.CompareTo(max) != 0) - { - if (typeof(T) == typeof(int)) - { - var minInt = min.To(); - var maxInt = max.To(); - - for (var i = 0; i < _data.Length; i++) - _data[i] = RandomGen.GetInt(minInt, maxInt).To(); - } - else if (typeof(T) == typeof(TimeSpan)) - { - var minTimeSpan = min.To(); - var maxTimeSpan = max.To(); - - for (var i = 0; i < _data.Length; i++) - _data[i] = RandomGen.GetTime(minTimeSpan, maxTimeSpan).To(); - } - else - throw new NotSupportedException(); - } - } + /// + /// Gets the maximum value used in the random array generation. + /// + public T Max { get; } - /// - /// Gets the total number of elements in the random array. - /// - public int Count { get; } - - /// - /// Gets the minimum value (or default seed value) used in the random array generation. - /// - public T Min { get; } - - /// - /// Gets the maximum value used in the random array generation. - /// - public T Max { get; } - - /// - /// Returns the next random element from the array. - /// - /// A random element of type T from the array. - public T Next() + /// + /// Returns the next random element from the array. + /// + /// A random element of type T from the array. + public T Next() + { + lock (_lock) { - lock (_lock) - { - var next = _data[_index++]; + var next = _data[_index++]; - if (_index == _data.Length) - _index = 0; + if (_index == _data.Length) + _index = 0; - return next; - } + return next; } } } \ No newline at end of file diff --git a/Common/RandomGen.cs b/Common/RandomGen.cs index a295608e..92ecb81d 100644 --- a/Common/RandomGen.cs +++ b/Common/RandomGen.cs @@ -1,270 +1,269 @@ -namespace Ecng.Common +namespace Ecng.Common; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Globalization; +using System.Runtime.CompilerServices; + +/// +/// Provides methods for generating random values of various types. +/// +public static class RandomGen { - using System; - using System.Collections.Generic; - using System.Linq; - using System.Globalization; - using System.Runtime.CompilerServices; + private static readonly SyncObject _sync = new(); + private static readonly Random _value = new((int)DateTime.Now.Ticks); /// - /// Provides methods for generating random values of various types. + /// Returns a random double value between 0.0 and 1.0. /// - public static class RandomGen + /// A random double. + public static double GetDouble() { - private static readonly SyncObject _sync = new(); - private static readonly Random _value = new((int)DateTime.Now.Ticks); - - /// - /// Returns a random double value between 0.0 and 1.0. - /// - /// A random double. - public static double GetDouble() - { - lock (_sync) - return _value.NextDouble(); - } + lock (_sync) + return _value.NextDouble(); + } - /// - /// Returns an array of random bytes with the specified count. - /// - /// The number of random bytes to generate. - /// An array of random bytes. - public static byte[] GetBytes(int count) - { - var buffer = new byte[count]; - GetBytes(buffer); - return buffer; - } + /// + /// Returns an array of random bytes with the specified count. + /// + /// The number of random bytes to generate. + /// An array of random bytes. + public static byte[] GetBytes(int count) + { + var buffer = new byte[count]; + GetBytes(buffer); + return buffer; + } - /// - /// Fills the provided array with random bytes. - /// - /// The array to fill with random bytes. - public static void GetBytes(byte[] buffer) - { - lock (_sync) - _value.NextBytes(buffer); - } + /// + /// Fills the provided array with random bytes. + /// + /// The array to fill with random bytes. + public static void GetBytes(byte[] buffer) + { + lock (_sync) + _value.NextBytes(buffer); + } - /// - /// Returns a random non-negative integer. - /// - /// A random integer. - public static int GetInt() - { - lock (_sync) - return _value.Next(); - } + /// + /// Returns a random non-negative integer. + /// + /// A random integer. + public static int GetInt() + { + lock (_sync) + return _value.Next(); + } - /// - /// Returns a random integer between 0 and the specified maximum value (inclusive). - /// - /// The maximum value. - /// A random integer between 0 and max (inclusive). - public static int GetInt(int max) + /// + /// Returns a random integer between 0 and the specified maximum value (inclusive). + /// + /// The maximum value. + /// A random integer between 0 and max (inclusive). + public static int GetInt(int max) + { + lock (_sync) + return _value.Next(max + 1); + } + + /// + /// Returns a random integer between the specified minimum and maximum values (inclusive). + /// + /// The minimum value. + /// The maximum value. + /// A random integer between min and max (inclusive). + public static int GetInt(int min, int max) + { + lock (_sync) + return _value.Next(min, max + 1); + } + + /// + /// Returns a random long value between long.MinValue and long.MaxValue. + /// + /// A random long value. + public static long GetLong() => GetLong(long.MinValue, long.MaxValue); + + /// + /// Returns a random long value between the specified minimum and maximum values (inclusive). + /// + /// The minimum value. + /// The maximum value. + /// A random long value between min and max (inclusive). + public static long GetLong(long min, long max) + { + //if (min >= int.MinValue && max <= int.MaxValue) + // return GetInt((int)min, (int)max); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static int Log2Ceiling(ulong value) { - lock (_sync) - return _value.Next(max + 1); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static int NumberOfSetBits(ulong i) + { + i -= (i >> 1) & 0x5555555555555555UL; + i = (i & 0x3333333333333333UL) + ((i >> 2) & 0x3333333333333333UL); + return (int)(unchecked(((i + (i >> 4)) & 0xF0F0F0F0F0F0F0FUL) * 0x101010101010101UL) >> 56); + } + + var result = (int)Math.Log(value, 2); + + if (NumberOfSetBits(value) != 1) + result++; + + return result; } - /// - /// Returns a random integer between the specified minimum and maximum values (inclusive). - /// - /// The minimum value. - /// The maximum value. - /// A random integer between min and max (inclusive). - public static int GetInt(int min, int max) + static ulong GetULong() { lock (_sync) - return _value.Next(min, max + 1); + { + return ((ulong)(uint)_value.Next(1 << 22)) | + (((ulong)(uint)_value.Next(1 << 22)) << 22) | + (((ulong)(uint)_value.Next(1 << 20)) << 44); + } } - /// - /// Returns a random long value between long.MinValue and long.MaxValue. - /// - /// A random long value. - public static long GetLong() => GetLong(long.MinValue, long.MaxValue); - - /// - /// Returns a random long value between the specified minimum and maximum values (inclusive). - /// - /// The minimum value. - /// The maximum value. - /// A random long value between min and max (inclusive). - public static long GetLong(long min, long max) + var exclusiveRange = (ulong)(max - min); + + if (exclusiveRange > 1) { - //if (min >= int.MinValue && max <= int.MaxValue) - // return GetInt((int)min, (int)max); + // Narrow down to the smallest range [0, 2^bits] that contains maxValue - minValue. + // Then repeatedly generate a value in that outer range until we get one within the inner range. + var bits = Log2Ceiling(exclusiveRange); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static int Log2Ceiling(ulong value) + while (true) { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static int NumberOfSetBits(ulong i) - { - i -= (i >> 1) & 0x5555555555555555UL; - i = (i & 0x3333333333333333UL) + ((i >> 2) & 0x3333333333333333UL); - return (int)(unchecked(((i + (i >> 4)) & 0xF0F0F0F0F0F0F0FUL) * 0x101010101010101UL) >> 56); - } + var result = GetULong() >> (sizeof(long) * 8 - bits); - var result = (int)Math.Log(value, 2); + if (result < exclusiveRange) + return (long)result + min; + } + } - if (NumberOfSetBits(value) != 1) - result++; + return min + GetInt((int)exclusiveRange); + } - return result; - } + /// + /// Returns a random boolean value. + /// + /// A random boolean. + public static bool GetBool() => GetInt(1) == 1; - static ulong GetULong() - { - lock (_sync) - { - return ((ulong)(uint)_value.Next(1 << 22)) | - (((ulong)(uint)_value.Next(1 << 22)) << 22) | - (((ulong)(uint)_value.Next(1 << 20)) << 44); - } - } + /// + /// Returns a random enum value of type T. + /// + /// The enum type. + /// A random enum value. + public static T GetEnum() + where T : struct + => GetEnum(Enumerator.GetValues()); - var exclusiveRange = (ulong)(max - min); + /// + /// Returns a random enum value from the specified collection of values. + /// + /// The enum type. + /// A collection of enum values. + /// A random enum value from the collection. + public static T GetEnum(IEnumerable values) + where T : struct + => GetEnum(default, values.Max(value => value.To()).To()); - if (exclusiveRange > 1) - { - // Narrow down to the smallest range [0, 2^bits] that contains maxValue - minValue. - // Then repeatedly generate a value in that outer range until we get one within the inner range. - var bits = Log2Ceiling(exclusiveRange); + /// + /// Returns a random enum value between the specified minimum and maximum enum values. + /// + /// The enum type. + /// The minimum enum value. + /// The maximum enum value. + /// A random enum value between min and max. + public static T GetEnum(T min, T max) + where T : struct + => GetLong(min.To(), max.To()).To(); - while (true) - { - var result = GetULong() >> (sizeof(long) * 8 - bits); + /// + /// Returns a random element from the specified collection. + /// + /// The type of elements in the collection. + /// The collection of elements. + /// A random element from the collection. + public static T GetElement(IEnumerable array) + => array.ElementAt(GetInt(0, array.Count() - 1)); - if (result < exclusiveRange) - return (long)result + min; - } - } + /// + /// Returns a random Base64 encoded string generated from a random salt. + /// + /// The minimum length for generating the salt. + /// The maximum length for generating the salt. + /// A random Base64 encoded string. + public static string GetString(int min, int max) + => TypeHelper.GenerateSalt(GetInt(min, max)).Base64(); - return min + GetInt((int)exclusiveRange); - } + /// + /// Returns a random DateTime value between DateTime.MinValue and DateTime.MaxValue. + /// + /// A random DateTime. + public static DateTime GetDate() + => GetDate(DateTime.MinValue, DateTime.MaxValue); - /// - /// Returns a random boolean value. - /// - /// A random boolean. - public static bool GetBool() => GetInt(1) == 1; - - /// - /// Returns a random enum value of type T. - /// - /// The enum type. - /// A random enum value. - public static T GetEnum() - where T : struct - => GetEnum(Enumerator.GetValues()); - - /// - /// Returns a random enum value from the specified collection of values. - /// - /// The enum type. - /// A collection of enum values. - /// A random enum value from the collection. - public static T GetEnum(IEnumerable values) - where T : struct - => GetEnum(default, values.Max(value => value.To()).To()); - - /// - /// Returns a random enum value between the specified minimum and maximum enum values. - /// - /// The enum type. - /// The minimum enum value. - /// The maximum enum value. - /// A random enum value between min and max. - public static T GetEnum(T min, T max) - where T : struct - => GetLong(min.To(), max.To()).To(); - - /// - /// Returns a random element from the specified collection. - /// - /// The type of elements in the collection. - /// The collection of elements. - /// A random element from the collection. - public static T GetElement(IEnumerable array) - => array.ElementAt(GetInt(0, array.Count() - 1)); - - /// - /// Returns a random Base64 encoded string generated from a random salt. - /// - /// The minimum length for generating the salt. - /// The maximum length for generating the salt. - /// A random Base64 encoded string. - public static string GetString(int min, int max) - => TypeHelper.GenerateSalt(GetInt(min, max)).Base64(); - - /// - /// Returns a random DateTime value between DateTime.MinValue and DateTime.MaxValue. - /// - /// A random DateTime. - public static DateTime GetDate() - => GetDate(DateTime.MinValue, DateTime.MaxValue); - - /// - /// Returns a random DateTime value between the specified minimum and maximum values. - /// - /// The minimum DateTime value. - /// The maximum DateTime value. - /// A random DateTime between min and max. - public static DateTime GetDate(DateTime min, DateTime max) - => min + GetTime(default, max - min); - - /// - /// Returns a random TimeSpan value between TimeSpan.MinValue and TimeSpan.MaxValue. - /// - /// A random TimeSpan. - public static TimeSpan GetTime() - => GetTime(TimeSpan.MinValue, TimeSpan.MaxValue); - - /// - /// Returns a random TimeSpan value between the specified minimum and maximum values. - /// - /// The minimum TimeSpan value. - /// The maximum TimeSpan value. - /// A random TimeSpan between min and max. - public static TimeSpan GetTime(TimeSpan min, TimeSpan max) - => GetLong(min.Ticks, max.Ticks).To(); - - /// - /// Returns a random non-zero decimal value with a specified number of integer and fractional digits. - /// Tries up to 10 times to generate a valid number. - /// - /// The maximum number of digits in the integer part. Default is 8. - /// The maximum number of digits in the fractional part. Default is 8. - /// A random decimal value. - /// Thrown when a valid decimal value cannot be generated in 10 attempts. - public static decimal GetDecimal(int integer = 8, int fractional = 8) - { - for (var k = 0; k < 10; k++) - { - var i1 = Enumerable.Repeat(9, GetInt(1, integer)).Select(i => GetInt(9).ToString()).Join(string.Empty).To(); - var i2 = Enumerable.Repeat(9, GetInt(1, fractional)).Select(i => GetInt(9).ToString()).Join(string.Empty).To(); - var value = decimal.Parse(i1 + "." + i2, CultureInfo.InvariantCulture); + /// + /// Returns a random DateTime value between the specified minimum and maximum values. + /// + /// The minimum DateTime value. + /// The maximum DateTime value. + /// A random DateTime between min and max. + public static DateTime GetDate(DateTime min, DateTime max) + => min + GetTime(default, max - min); - if (value != 0) - return value; - } + /// + /// Returns a random TimeSpan value between TimeSpan.MinValue and TimeSpan.MaxValue. + /// + /// A random TimeSpan. + public static TimeSpan GetTime() + => GetTime(TimeSpan.MinValue, TimeSpan.MaxValue); - throw new InvalidOperationException(); - } + /// + /// Returns a random TimeSpan value between the specified minimum and maximum values. + /// + /// The minimum TimeSpan value. + /// The maximum TimeSpan value. + /// A random TimeSpan between min and max. + public static TimeSpan GetTime(TimeSpan min, TimeSpan max) + => GetLong(min.Ticks, max.Ticks).To(); - /// - /// Returns a random decimal value between the specified minimum and maximum values with the given precision. - /// - /// The minimum decimal value. - /// The maximum decimal value. - /// The number of decimal places to round the value to. - /// A random decimal value between min and max rounded to the specified precision. - public static decimal GetDecimal(decimal min, decimal max, int precision) + /// + /// Returns a random non-zero decimal value with a specified number of integer and fractional digits. + /// Tries up to 10 times to generate a valid number. + /// + /// The maximum number of digits in the integer part. Default is 8. + /// The maximum number of digits in the fractional part. Default is 8. + /// A random decimal value. + /// Thrown when a valid decimal value cannot be generated in 10 attempts. + public static decimal GetDecimal(int integer = 8, int fractional = 8) + { + for (var k = 0; k < 10; k++) { - var value = GetDouble() * ((double)max - (double)min) + (double)min; - return (decimal)value.Round(precision); + var i1 = Enumerable.Repeat(9, GetInt(1, integer)).Select(i => GetInt(9).ToString()).Join(string.Empty).To(); + var i2 = Enumerable.Repeat(9, GetInt(1, fractional)).Select(i => GetInt(9).ToString()).Join(string.Empty).To(); + var value = decimal.Parse(i1 + "." + i2, CultureInfo.InvariantCulture); + + if (value != 0) + return value; } + + throw new InvalidOperationException(); + } + + /// + /// Returns a random decimal value between the specified minimum and maximum values with the given precision. + /// + /// The minimum decimal value. + /// The maximum decimal value. + /// The number of decimal places to round the value to. + /// A random decimal value between min and max rounded to the specified precision. + public static decimal GetDecimal(decimal min, decimal max, int precision) + { + var value = GetDouble() * ((double)max - (double)min) + (double)min; + return (decimal)value.Round(precision); } } \ No newline at end of file diff --git a/Common/Ref Tuples.cs b/Common/Ref Tuples.cs index 666c8583..15bed52b 100644 --- a/Common/Ref Tuples.cs +++ b/Common/Ref Tuples.cs @@ -1,335 +1,334 @@ -namespace Ecng.Common +namespace Ecng.Common; + +using System; +using System.Linq; +using System.Collections.Generic; + +/// +/// Represents a reference tuple with enumerable object values. +/// +public interface IRefTuple { - using System; - using System.Linq; - using System.Collections.Generic; + /// + /// Gets or sets the values of the tuple. + /// + IEnumerable Values { get; set; } +} +/// +/// Represents a pair of reference values. +/// +/// Type of the first element. +/// Type of the second element. +public class RefPair : IRefTuple +{ /// - /// Represents a reference tuple with enumerable object values. + /// Initializes a new instance of the class. /// - public interface IRefTuple + public RefPair() { - /// - /// Gets or sets the values of the tuple. - /// - IEnumerable Values { get; set; } } /// - /// Represents a pair of reference values. + /// Initializes a new instance of the class with specified values. /// - /// Type of the first element. - /// Type of the second element. - public class RefPair : IRefTuple + /// The first element. + /// The second element. + public RefPair(TFirst first, TSecond second) { - /// - /// Initializes a new instance of the class. - /// - public RefPair() - { - } - - /// - /// Initializes a new instance of the class with specified values. - /// - /// The first element. - /// The second element. - public RefPair(TFirst first, TSecond second) - { - First = first; - Second = second; - } + First = first; + Second = second; + } - /// - /// Gets or sets the first element. - /// - public TFirst First { get; set; } + /// + /// Gets or sets the first element. + /// + public TFirst First { get; set; } - /// - /// Gets or sets the second element. - /// - public TSecond Second { get; set; } + /// + /// Gets or sets the second element. + /// + public TSecond Second { get; set; } - /// - /// Gets or sets the tuple values. - /// - public virtual IEnumerable Values + /// + /// Gets or sets the tuple values. + /// + public virtual IEnumerable Values + { + get => [First, Second]; + set { - get => [First, Second]; - set - { - First = (TFirst)value.ElementAt(0); - Second = (TSecond)value.ElementAt(1); - } + First = (TFirst)value.ElementAt(0); + Second = (TSecond)value.ElementAt(1); } + } - /// - /// Returns a string that represents the tuple. - /// - /// A string representation of the tuple. - public override string ToString() - => "[" + GetValuesString() + "]"; + /// + /// Returns a string that represents the tuple. + /// + /// A string representation of the tuple. + public override string ToString() + => "[" + GetValuesString() + "]"; - /// - /// Gets a string that represents the tuple values. - /// - /// A string representation of the tuple values. - protected virtual string GetValuesString() - => First + ", " + Second; + /// + /// Gets a string that represents the tuple values. + /// + /// A string representation of the tuple values. + protected virtual string GetValuesString() + => First + ", " + Second; - /// - /// Converts the tuple to a KeyValuePair. - /// - /// A KeyValuePair containing the first and second elements. - public KeyValuePair ToValuePair() - => new(First, Second); - } + /// + /// Converts the tuple to a KeyValuePair. + /// + /// A KeyValuePair containing the first and second elements. + public KeyValuePair ToValuePair() + => new(First, Second); +} +/// +/// Represents a triple of reference values. +/// +/// Type of the first element. +/// Type of the second element. +/// Type of the third element. +public class RefTriple : RefPair +{ /// - /// Represents a triple of reference values. + /// Initializes a new instance of the class. /// - /// Type of the first element. - /// Type of the second element. - /// Type of the third element. - public class RefTriple : RefPair + public RefTriple() { - /// - /// Initializes a new instance of the class. - /// - public RefTriple() - { - } + } - /// - /// Initializes a new instance of the class with specified values. - /// - /// The first element. - /// The second element. - /// The third element. - public RefTriple(TFirst first, TSecond second, TThird third) - : base(first, second) - { - Third = third; - } + /// + /// Initializes a new instance of the class with specified values. + /// + /// The first element. + /// The second element. + /// The third element. + public RefTriple(TFirst first, TSecond second, TThird third) + : base(first, second) + { + Third = third; + } - /// - /// Gets or sets the third element. - /// - public TThird Third { get; set; } + /// + /// Gets or sets the third element. + /// + public TThird Third { get; set; } - /// - /// Gets or sets the tuple values. - /// - public override IEnumerable Values + /// + /// Gets or sets the tuple values. + /// + public override IEnumerable Values + { + get => base.Values.Concat([Third]); + set { - get => base.Values.Concat([Third]); - set - { - base.Values = value; - Third = (TThird)value.ElementAt(2); - } + base.Values = value; + Third = (TThird)value.ElementAt(2); } - - /// - /// Gets a string that represents the tuple values. - /// - /// A string representation of the tuple values. - protected override string GetValuesString() - => base.GetValuesString() + ", " + Third; } /// - /// Represents a quadruple of reference values. + /// Gets a string that represents the tuple values. /// - /// Type of the first element. - /// Type of the second element. - /// Type of the third element. - /// Type of the fourth element. - public class RefQuadruple : RefTriple + /// A string representation of the tuple values. + protected override string GetValuesString() + => base.GetValuesString() + ", " + Third; +} + +/// +/// Represents a quadruple of reference values. +/// +/// Type of the first element. +/// Type of the second element. +/// Type of the third element. +/// Type of the fourth element. +public class RefQuadruple : RefTriple +{ + /// + /// Initializes a new instance of the class. + /// + public RefQuadruple() { - /// - /// Initializes a new instance of the class. - /// - public RefQuadruple() - { - } + } - /// - /// Initializes a new instance of the class with specified values. - /// - /// The first element. - /// The second element. - /// The third element. - /// The fourth element. - public RefQuadruple(TFirst first, TSecond second, TThird third, TFourth fourth) - : base(first, second, third) - { - Fourth = fourth; - } + /// + /// Initializes a new instance of the class with specified values. + /// + /// The first element. + /// The second element. + /// The third element. + /// The fourth element. + public RefQuadruple(TFirst first, TSecond second, TThird third, TFourth fourth) + : base(first, second, third) + { + Fourth = fourth; + } - /// - /// Gets or sets the fourth element. - /// - public TFourth Fourth { get; set; } + /// + /// Gets or sets the fourth element. + /// + public TFourth Fourth { get; set; } - /// - /// Gets or sets the tuple values. - /// - public override IEnumerable Values + /// + /// Gets or sets the tuple values. + /// + public override IEnumerable Values + { + get => base.Values.Concat([Fourth]); + set { - get => base.Values.Concat([Fourth]); - set - { - base.Values = value; - Fourth = (TFourth)value.ElementAt(3); - } + base.Values = value; + Fourth = (TFourth)value.ElementAt(3); } - - /// - /// Gets a string that represents the tuple values. - /// - /// A string representation of the tuple values. - protected override string GetValuesString() - => base.GetValuesString() + ", " + Fourth; } /// - /// Represents a quintuple of reference values. + /// Gets a string that represents the tuple values. /// - /// Type of the first element. - /// Type of the second element. - /// Type of the third element. - /// Type of the fourth element. - /// Type of the fifth element. - public class RefFive : RefQuadruple + /// A string representation of the tuple values. + protected override string GetValuesString() + => base.GetValuesString() + ", " + Fourth; +} + +/// +/// Represents a quintuple of reference values. +/// +/// Type of the first element. +/// Type of the second element. +/// Type of the third element. +/// Type of the fourth element. +/// Type of the fifth element. +public class RefFive : RefQuadruple +{ + /// + /// Initializes a new instance of the class. + /// + public RefFive() { - /// - /// Initializes a new instance of the class. - /// - public RefFive() - { - } + } - /// - /// Initializes a new instance of the class with specified values. - /// - /// The first element. - /// The second element. - /// The third element. - /// The fourth element. - /// The fifth element. - public RefFive(TFirst first, TSecond second, TThird third, TFourth fourth, TFifth fifth) - : base(first, second, third, fourth) - { - Fifth = fifth; - } + /// + /// Initializes a new instance of the class with specified values. + /// + /// The first element. + /// The second element. + /// The third element. + /// The fourth element. + /// The fifth element. + public RefFive(TFirst first, TSecond second, TThird third, TFourth fourth, TFifth fifth) + : base(first, second, third, fourth) + { + Fifth = fifth; + } - /// - /// Gets or sets the fifth element. - /// - public TFifth Fifth { get; set; } + /// + /// Gets or sets the fifth element. + /// + public TFifth Fifth { get; set; } - /// - /// Gets or sets the tuple values. - /// - public override IEnumerable Values + /// + /// Gets or sets the tuple values. + /// + public override IEnumerable Values + { + get => base.Values.Concat([Fifth]); + set { - get => base.Values.Concat([Fifth]); - set - { - base.Values = value; - Fifth = (TFifth)value.ElementAt(4); - } + base.Values = value; + Fifth = (TFifth)value.ElementAt(4); } - - /// - /// Gets a string that represents the tuple values. - /// - /// A string representation of the tuple values. - protected override string GetValuesString() - => base.GetValuesString() + ", " + Fifth; } /// - /// Provides factory methods to create reference tuples. + /// Gets a string that represents the tuple values. /// - public static class RefTuple - { - /// - /// Creates a new pair of reference values. - /// - /// Type of the first element. - /// Type of the second element. - /// The first element. - /// The second element. - /// A new instance of . - public static RefPair Create(TFirst first, TSecond second) - => new(first, second); + /// A string representation of the tuple values. + protected override string GetValuesString() + => base.GetValuesString() + ", " + Fifth; +} - /// - /// Creates a new triple of reference values. - /// - /// Type of the first element. - /// Type of the second element. - /// Type of the third element. - /// The first element. - /// The second element. - /// The third element. - /// A new instance of . - public static RefTriple Create(TFirst first, TSecond second, TThird third) - => new(first, second, third); +/// +/// Provides factory methods to create reference tuples. +/// +public static class RefTuple +{ + /// + /// Creates a new pair of reference values. + /// + /// Type of the first element. + /// Type of the second element. + /// The first element. + /// The second element. + /// A new instance of . + public static RefPair Create(TFirst first, TSecond second) + => new(first, second); - /// - /// Creates a new quadruple of reference values. - /// - /// Type of the first element. - /// Type of the second element. - /// Type of the third element. - /// Type of the fourth element. - /// The first element. - /// The second element. - /// The third element. - /// The fourth element. - /// A new instance of . - public static RefQuadruple Create(TFirst first, TSecond second, TThird third, TFourth fourth) - => new(first, second, third, fourth); + /// + /// Creates a new triple of reference values. + /// + /// Type of the first element. + /// Type of the second element. + /// Type of the third element. + /// The first element. + /// The second element. + /// The third element. + /// A new instance of . + public static RefTriple Create(TFirst first, TSecond second, TThird third) + => new(first, second, third); + + /// + /// Creates a new quadruple of reference values. + /// + /// Type of the first element. + /// Type of the second element. + /// Type of the third element. + /// Type of the fourth element. + /// The first element. + /// The second element. + /// The third element. + /// The fourth element. + /// A new instance of . + public static RefQuadruple Create(TFirst first, TSecond second, TThird third, TFourth fourth) + => new(first, second, third, fourth); - /// - /// Creates a new quintuple of reference values. - /// - /// Type of the first element. - /// Type of the second element. - /// Type of the third element. - /// Type of the fourth element. - /// Type of the fifth element. - /// The first element. - /// The second element. - /// The third element. - /// The fourth element. - /// The fifth element. - /// A new instance of . - public static RefFive Create(TFirst first, TSecond second, TThird third, TFourth fourth, TFifth fifth) - => new(first, second, third, fourth, fifth); + /// + /// Creates a new quintuple of reference values. + /// + /// Type of the first element. + /// Type of the second element. + /// Type of the third element. + /// Type of the fourth element. + /// Type of the fifth element. + /// The first element. + /// The second element. + /// The third element. + /// The fourth element. + /// The fifth element. + /// A new instance of . + public static RefFive Create(TFirst first, TSecond second, TThird third, TFourth fourth, TFifth fifth) + => new(first, second, third, fourth, fifth); - private static readonly RefFive _t = new(); + private static readonly RefFive _t = new(); - /// - /// Gets the name of the member at the specified index. - /// - /// The index of the member. - /// The name of the corresponding member. - /// Thrown when the index is out of range. - public static string GetName(int idx) + /// + /// Gets the name of the member at the specified index. + /// + /// The index of the member. + /// The name of the corresponding member. + /// Thrown when the index is out of range. + public static string GetName(int idx) + { + return idx switch { - return idx switch - { - 0 => nameof(_t.First), - 1 => nameof(_t.Second), - 2 => nameof(_t.Third), - 3 => nameof(_t.Fourth), - 4 => nameof(_t.Fifth), - _ => throw new ArgumentOutOfRangeException(nameof(idx)), - }; - } + 0 => nameof(_t.First), + 1 => nameof(_t.Second), + 2 => nameof(_t.Third), + 3 => nameof(_t.Fourth), + 4 => nameof(_t.Fifth), + _ => throw new ArgumentOutOfRangeException(nameof(idx)), + }; } } \ No newline at end of file diff --git a/Common/ResettableTimer.cs b/Common/ResettableTimer.cs index 5e4449a4..c858a474 100644 --- a/Common/ResettableTimer.cs +++ b/Common/ResettableTimer.cs @@ -1,122 +1,121 @@ -namespace Ecng.Common +namespace Ecng.Common; + +using System; + +/// +/// Represents a timer that can be reset and activated repeatedly. +/// The timer executes the Elapsed event periodically based on the specified period. +/// +public class ResettableTimer(TimeSpan period, string name) : Disposable { - using System; + private readonly SyncObject _sync = new(); + private readonly SyncObject _finish = new(); + private bool _isActivated; + private bool _isFinished = true; + private bool _isCancelled; + + private readonly TimeSpan _period = period; + private readonly string _name = name; /// - /// Represents a timer that can be reset and activated repeatedly. - /// The timer executes the Elapsed event periodically based on the specified period. + /// Occurs when the timer interval has elapsed. + /// The event receives a function that determines if processing can be executed. /// - public class ResettableTimer(TimeSpan period, string name) : Disposable - { - private readonly SyncObject _sync = new(); - private readonly SyncObject _finish = new(); - private bool _isActivated; - private bool _isFinished = true; - private bool _isCancelled; - - private readonly TimeSpan _period = period; - private readonly string _name = name; - - /// - /// Occurs when the timer interval has elapsed. - /// The event receives a function that determines if processing can be executed. - /// - public event Action> Elapsed; + public event Action> Elapsed; - /// - /// Activates the timer. - /// If the timer is not already running, starts a new thread that periodically raises the Elapsed event. - /// - public void Activate() + /// + /// Activates the timer. + /// If the timer is not already running, starts a new thread that periodically raises the Elapsed event. + /// + public void Activate() + { + lock (_sync) { - lock (_sync) - { - _isActivated = true; + _isActivated = true; - if (!_isFinished) - return; + if (!_isFinished) + return; - _isFinished = false; - _isCancelled = false; - } + _isFinished = false; + _isCancelled = false; + } - ThreadingHelper.Thread(() => + ThreadingHelper.Thread(() => + { + try { - try + while (!IsDisposed) { - while (!IsDisposed) + lock (_sync) { - lock (_sync) - { - _isCancelled = false; + _isCancelled = false; - if (_isActivated) - _isActivated = false; - else - { - _isFinished = true; - break; - } + if (_isActivated) + _isActivated = false; + else + { + _isFinished = true; + break; } - - Elapsed?.Invoke(CanProcess); - _period.Sleep(); } - } - finally - { - _finish.PulseAll(); - } - }).Name(_name).Launch(); - } - - /// - /// Cancels the current timer operation. - /// If the timer is running, this method signals that the current cycle should not process further. - /// - public void Cancel() - { - lock (_sync) - { - if (_isFinished) - return; - _isActivated = false; - _isCancelled = true; + Elapsed?.Invoke(CanProcess); + _period.Sleep(); + } } - } - - /// - /// Flushes the timer by activating it and then waiting for the ongoing timer cycle to finish. - /// - public void Flush() - { - lock (_finish) + finally { - Activate(); - _finish.Wait(); + _finish.PulseAll(); } - } + }).Name(_name).Launch(); + } - /// - /// Determines if the timer can process the Elapsed event based on its cancellation or disposal state. - /// - /// - /// True if the timer is not cancelled and not disposed; otherwise, false. - /// - private bool CanProcess() + /// + /// Cancels the current timer operation. + /// If the timer is running, this method signals that the current cycle should not process further. + /// + public void Cancel() + { + lock (_sync) { - return !_isCancelled && !IsDisposed; + if (_isFinished) + return; + + _isActivated = false; + _isCancelled = true; } + } - /// - /// Releases the managed resources used by the timer. - /// Cancels the timer operation before disposing. - /// - protected override void DisposeManaged() + /// + /// Flushes the timer by activating it and then waiting for the ongoing timer cycle to finish. + /// + public void Flush() + { + lock (_finish) { - Cancel(); - base.DisposeManaged(); + Activate(); + _finish.Wait(); } } + + /// + /// Determines if the timer can process the Elapsed event based on its cancellation or disposal state. + /// + /// + /// True if the timer is not cancelled and not disposed; otherwise, false. + /// + private bool CanProcess() + { + return !_isCancelled && !IsDisposed; + } + + /// + /// Releases the managed resources used by the timer. + /// Cancels the timer operation before disposing. + /// + protected override void DisposeManaged() + { + Cancel(); + base.DisposeManaged(); + } } \ No newline at end of file diff --git a/Common/Scope.cs b/Common/Scope.cs index 71188fb3..848392c7 100644 --- a/Common/Scope.cs +++ b/Common/Scope.cs @@ -1,145 +1,144 @@ -namespace Ecng.Common -{ - #region Using Directives +namespace Ecng.Common; - using System; - using System.Collections.Generic; - using System.Security; - using System.Threading; +#region Using Directives - #endregion +using System; +using System.Collections.Generic; +using System.Security; +using System.Threading; + +#endregion + +/// +/// Provides a scope for managing a resource with automatic disposal. +/// +/// The type of the resource to be managed. +public sealed class Scope : Disposable + //where T : class +{ + #region Scope.ctor() /// - /// Provides a scope for managing a resource with automatic disposal. + /// Initializes a new instance of the class with a new instance of T. /// - /// The type of the resource to be managed. - public sealed class Scope : Disposable - //where T : class + public Scope() + : this(Activator.CreateInstance(), true) { - #region Scope.ctor() - - /// - /// Initializes a new instance of the class with a new instance of T. - /// - public Scope() - : this(Activator.CreateInstance(), true) - { - } + } - /// - /// Initializes a new instance of the class with the specified value. - /// - /// The instance of T to be managed. - public Scope(T value) - : this(value, true) - { - } + /// + /// Initializes a new instance of the class with the specified value. + /// + /// The instance of T to be managed. + public Scope(T value) + : this(value, true) + { + } - /// - /// Initializes a new instance of the class with the specified value and ownership. - /// - /// The instance of T to be managed. - /// Indicates whether the scope owns the instance and is responsible for disposing it. - public Scope(T value, bool ownInstance) - { - if (value.IsNull()) - throw new ArgumentNullException(nameof(value)); + /// + /// Initializes a new instance of the class with the specified value and ownership. + /// + /// The instance of T to be managed. + /// Indicates whether the scope owns the instance and is responsible for disposing it. + public Scope(T value, bool ownInstance) + { + if (value.IsNull()) + throw new ArgumentNullException(nameof(value)); - Value = value; - OwnInstance = ownInstance; + Value = value; + OwnInstance = ownInstance; - Parent = _current.Value; - _current.Value = this; + Parent = _current.Value; + _current.Value = this; - //_all.Add(this); - } + //_all.Add(this); + } - #endregion + #endregion - #region Parent + #region Parent - /// - /// Gets the parent scope of the current scope. - /// - public Scope Parent { get; } + /// + /// Gets the parent scope of the current scope. + /// + public Scope Parent { get; } - #region Current + #region Current - // ReSharper disable once InconsistentNaming - private static readonly AsyncLocal> _current = new(); + // ReSharper disable once InconsistentNaming + private static readonly AsyncLocal> _current = new(); - /// - /// Gets the current scope. - /// - public static Scope Current => _current.Value; + /// + /// Gets the current scope. + /// + public static Scope Current => _current.Value; - /// - /// Gets a value indicating whether a current scope is defined. - /// - public static bool IsDefined => Current != null; + /// + /// Gets a value indicating whether a current scope is defined. + /// + public static bool IsDefined => Current != null; - #endregion + #endregion - /// - /// Gets all scopes in the current hierarchy as a collection. - /// - public static ICollection> All + /// + /// Gets all scopes in the current hierarchy as a collection. + /// + public static ICollection> All + { + get { - get - { - var all = new List>(); - - var current = Current; - while (current != null) - { - all.Add(current); - current = current.Parent; - } + var all = new List>(); - all.Reverse(); - - return all; + var current = Current; + while (current != null) + { + all.Add(current); + current = current.Parent; } - } - /// - /// Gets a value indicating whether the scope owns the managed instance. - /// - public bool OwnInstance { get; } + all.Reverse(); - /// - /// Gets the value contained in the scope. - /// - public T Value { get; } + return all; + } + } - #endregion + /// + /// Gets a value indicating whether the scope owns the managed instance. + /// + public bool OwnInstance { get; } - #region Disposable Members + /// + /// Gets the value contained in the scope. + /// + public T Value { get; } - /// - /// Disposes the managed resources used by the class. - /// - /// - /// Throws an if disposed out of order. - /// - [SecuritySafeCritical] - protected override void DisposeManaged() - { - if (this != _current.Value) - throw new InvalidOperationException("Disposed out of order."); + #endregion - _current.Value = Parent; + #region Disposable Members - if (OwnInstance) - { - Value.DoDispose(); - } + /// + /// Disposes the managed resources used by the class. + /// + /// + /// Throws an if disposed out of order. + /// + [SecuritySafeCritical] + protected override void DisposeManaged() + { + if (this != _current.Value) + throw new InvalidOperationException("Disposed out of order."); - //_all.Remove(this); + _current.Value = Parent; - base.DisposeManaged(); + if (OwnInstance) + { + Value.DoDispose(); } - #endregion + //_all.Remove(this); + + base.DisposeManaged(); } + + #endregion } diff --git a/Common/SimpleResettableTimer.cs b/Common/SimpleResettableTimer.cs index bbfc4b94..5ceb1677 100644 --- a/Common/SimpleResettableTimer.cs +++ b/Common/SimpleResettableTimer.cs @@ -1,99 +1,98 @@ -namespace Ecng.Common +namespace Ecng.Common; + +using System; +using System.Threading; + +/// +/// Represents a simple timer that can be reset. +/// When the timer period elapses without a reset, the event is invoked. +/// +public class SimpleResettableTimer(TimeSpan period) : IDisposable { - using System; - using System.Threading; + private readonly SyncObject _sync = new(); + private readonly TimeSpan _period = period; + + private Timer _timer; + private bool _changed; /// - /// Represents a simple timer that can be reset. - /// When the timer period elapses without a reset, the event is invoked. + /// Occurs when the timer elapses without being reset. /// - public class SimpleResettableTimer(TimeSpan period) : IDisposable - { - private readonly SyncObject _sync = new(); - private readonly TimeSpan _period = period; - - private Timer _timer; - private bool _changed; + public event Action Elapsed; - /// - /// Occurs when the timer elapses without being reset. - /// - public event Action Elapsed; - - /// - /// Resets the timer. If the timer is not already running, it starts the timer with the specified period. - /// If it is running, it marks that the timer should restart the count. - /// - public void Reset() + /// + /// Resets the timer. If the timer is not already running, it starts the timer with the specified period. + /// If it is running, it marks that the timer should restart the count. + /// + public void Reset() + { + lock (_sync) { - lock (_sync) + if (_timer is null) { - if (_timer is null) - { - _timer = ThreadingHelper - .Timer(OnTimer) - .Interval(_period); - } - else - _changed = true; + _timer = ThreadingHelper + .Timer(OnTimer) + .Interval(_period); } + else + _changed = true; } + } - private void OnTimer() - { - var elapsed = false; + private void OnTimer() + { + var elapsed = false; - lock (_sync) + lock (_sync) + { + if (!_changed) { - if (!_changed) + if (_timer != null) { - if (_timer != null) - { - _timer.Dispose(); - _timer = null; - } - - elapsed = true; + _timer.Dispose(); + _timer = null; } - else - _changed = false; - } - if (elapsed) - Elapsed?.Invoke(); + elapsed = true; + } + else + _changed = false; } - /// - /// Forces the timer to immediately run its elapsed logic if it is running, - /// effectively flushing the timer cycle. - /// - public void Flush() + if (elapsed) + Elapsed?.Invoke(); + } + + /// + /// Forces the timer to immediately run its elapsed logic if it is running, + /// effectively flushing the timer cycle. + /// + public void Flush() + { + lock (_sync) { - lock (_sync) - { - if (_timer is null) - return; + if (_timer is null) + return; - _changed = false; - _timer.Change(TimeSpan.Zero, _period); - } + _changed = false; + _timer.Change(TimeSpan.Zero, _period); } + } - /// - /// Disposes the timer and stops any further executions. - /// - public void Dispose() + /// + /// Disposes the timer and stops any further executions. + /// + public void Dispose() + { + lock (_sync) { - lock (_sync) - { - if (_timer is null) - return; + if (_timer is null) + return; - _changed = true; - - _timer.Dispose(); - _timer = null; - } + _changed = true; + + _timer.Dispose(); + _timer = null; } } } \ No newline at end of file diff --git a/Common/StringHelper.cs b/Common/StringHelper.cs index 3798151c..4bd9dde5 100644 --- a/Common/StringHelper.cs +++ b/Common/StringHelper.cs @@ -1,1838 +1,1838 @@ -namespace Ecng.Common +namespace Ecng.Common; + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Security; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using System.Collections; +using System.Reflection; + +using SmartFormat; +using SmartFormat.Core.Extensions; + +/// +/// Provides helper methods for string operations. +/// +public static class StringHelper { - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Runtime.InteropServices; - using System.Security; - using System.Text; - using System.Text.RegularExpressions; - using System.Threading; - using System.Threading.Tasks; - using System.Collections; - using System.Reflection; - - using SmartFormat; - using SmartFormat.Core.Extensions; - - /// - /// Provides helper methods for string operations. - /// - public static class StringHelper + private class DictionarySourceEx : ISource { - private class DictionarySourceEx : ISource - { - private readonly SyncObject _sync = new(); - private readonly Dictionary _genericTypes = []; - private readonly Dictionary _keys = []; + private readonly SyncObject _sync = new(); + private readonly Dictionary _genericTypes = []; + private readonly Dictionary _keys = []; - bool ISource.TryEvaluateSelector(ISelectorInfo selectorInfo) - { - if (selectorInfo.CurrentValue is not IDictionary dictionary) - return false; + bool ISource.TryEvaluateSelector(ISelectorInfo selectorInfo) + { + if (selectorInfo.CurrentValue is not IDictionary dictionary) + return false; - var dictType = dictionary.GetType(); + var dictType = dictionary.GetType(); - Type type; + Type type; - lock (_sync) + lock (_sync) + { + if (!_genericTypes.TryGetValue(dictType, out type)) { - if (!_genericTypes.TryGetValue(dictType, out type)) - { - type = dictType.GetGenericType(typeof(IDictionary<,>)); - _genericTypes.Add(dictType, type); - } + type = dictType.GetGenericType(typeof(IDictionary<,>)); + _genericTypes.Add(dictType, type); } + } - if (type is null) - return false; + if (type is null) + return false; - object key; - var text = selectorInfo.SelectorText; + object key; + var text = selectorInfo.SelectorText; - lock (_sync) + lock (_sync) + { + if (!_keys.TryGetValue(text, out key)) { - if (!_keys.TryGetValue(text, out key)) - { - key = text.To(type.GetGenericArguments()[0]); - _keys.Add(text, key); - } + key = text.To(type.GetGenericArguments()[0]); + _keys.Add(text, key); } - - selectorInfo.Result = dictionary[key]; - return true; } + + selectorInfo.Result = dictionary[key]; + return true; } + } - static StringHelper() - { - Smart.Default.AddExtensions(new DictionarySourceEx()); + static StringHelper() + { + Smart.Default.AddExtensions(new DictionarySourceEx()); - // https://stackoverflow.com/a/47017180 - Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); - } + // https://stackoverflow.com/a/47017180 + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + } - /// - /// Checks if the string is null or empty. - /// - /// The string to check. - /// True if the string is null or empty; otherwise, false. - public static bool IsEmpty(this string str) - { - return string.IsNullOrEmpty(str); - } + /// + /// Checks if the string is null or empty. + /// + /// The string to check. + /// True if the string is null or empty; otherwise, false. + public static bool IsEmpty(this string str) + { + return string.IsNullOrEmpty(str); + } - /// - /// Returns default value if the string is null or empty; otherwise, returns the original string. - /// - /// The string to check. - /// The value to return if string is null or empty. - /// Default value or original string. - public static string IsEmpty(this string str, string defaultValue) - { - return str.IsEmpty() ? defaultValue : str; - } + /// + /// Returns default value if the string is null or empty; otherwise, returns the original string. + /// + /// The string to check. + /// The value to return if string is null or empty. + /// Default value or original string. + public static string IsEmpty(this string str, string defaultValue) + { + return str.IsEmpty() ? defaultValue : str; + } - /// - /// Throws ArgumentNullException if the string is null or empty. - /// - /// The string to check. - /// The parameter name to use in the exception. - /// The original string if not empty. - /// Thrown when string is null or empty. - public static string ThrowIfEmpty(this string str, string paramName) - => str.IsEmpty() ? throw new ArgumentNullException(paramName) : str; - - /// - /// Checks if the string is null, empty, or consists only of white-space characters. - /// - /// The string to check. - /// True if the string is null, empty, or whitespace; otherwise, false. - public static bool IsEmptyOrWhiteSpace(this string str) - { - return string.IsNullOrWhiteSpace(str); - } + /// + /// Throws ArgumentNullException if the string is null or empty. + /// + /// The string to check. + /// The parameter name to use in the exception. + /// The original string if not empty. + /// Thrown when string is null or empty. + public static string ThrowIfEmpty(this string str, string paramName) + => str.IsEmpty() ? throw new ArgumentNullException(paramName) : str; - /// - /// Returns default value if the string is null, empty, or whitespace; otherwise, returns the original string. - /// - /// The string to check. - /// The value to return if string is null, empty, or whitespace. - /// Default value or original string. - public static string IsEmptyOrWhiteSpace(this string str, string defaultValue) - { - return str.IsEmptyOrWhiteSpace() ? defaultValue : str; - } + /// + /// Checks if the string is null, empty, or consists only of white-space characters. + /// + /// The string to check. + /// True if the string is null, empty, or whitespace; otherwise, false. + public static bool IsEmptyOrWhiteSpace(this string str) + { + return string.IsNullOrWhiteSpace(str); + } - /// - /// Formats the string using standard string.Format with the provided arguments. - /// - /// The format string. - /// The arguments to format the string with. - /// A formatted string. - /// Thrown when args is null. - public static string Put(this string str, params object[] args) - { - if (args is null) - throw new ArgumentNullException(nameof(args)); - return args.Length == 0 ? str : string.Format(str, args); - } + /// + /// Returns default value if the string is null, empty, or whitespace; otherwise, returns the original string. + /// + /// The string to check. + /// The value to return if string is null, empty, or whitespace. + /// Default value or original string. + public static string IsEmptyOrWhiteSpace(this string str, string defaultValue) + { + return str.IsEmptyOrWhiteSpace() ? defaultValue : str; + } - /// - /// Formats the string using Smart.Format with the provided arguments. - /// - /// The format string. - /// The arguments to format the string with. - /// A formatted string. - /// Thrown when args is null. - public static string PutEx(this string str, params object[] args) - { - if (args is null) - throw new ArgumentNullException(nameof(args)); + /// + /// Formats the string using standard string.Format with the provided arguments. + /// + /// The format string. + /// The arguments to format the string with. + /// A formatted string. + /// Thrown when args is null. + public static string Put(this string str, params object[] args) + { + if (args is null) + throw new ArgumentNullException(nameof(args)); + return args.Length == 0 ? str : string.Format(str, args); + } - return args.Length == 0 ? str : Smart.Format(str, args); - } + /// + /// Formats the string using Smart.Format with the provided arguments. + /// + /// The format string. + /// The arguments to format the string with. + /// A formatted string. + /// Thrown when args is null. + public static string PutEx(this string str, params object[] args) + { + if (args is null) + throw new ArgumentNullException(nameof(args)); - /// - /// Asynchronously formats the string using Smart.Format with the provided arguments. - /// - /// The format string. - /// The arguments to format the string with. - /// A token to cancel the asynchronous operation. - /// A ValueTask containing the formatted string. - /// Thrown when args is null. - public static ValueTask PutExAsync(this string str, object[] args, CancellationToken cancellationToken) - { - if (args is null) - throw new ArgumentNullException(nameof(args)); + return args.Length == 0 ? str : Smart.Format(str, args); + } - return args.Length == 0 ? new(str) : Smart.FormatAsync(str, args, cancellationToken); - } + /// + /// Asynchronously formats the string using Smart.Format with the provided arguments. + /// + /// The format string. + /// The arguments to format the string with. + /// A token to cancel the asynchronous operation. + /// A ValueTask containing the formatted string. + /// Thrown when args is null. + public static ValueTask PutExAsync(this string str, object[] args, CancellationToken cancellationToken) + { + if (args is null) + throw new ArgumentNullException(nameof(args)); - private static Type GetGenericType(this Type targetType, Type genericType) - { - if (targetType is null) - throw new ArgumentNullException(nameof(targetType)); + return args.Length == 0 ? new(str) : Smart.FormatAsync(str, args, cancellationToken); + } + + private static Type GetGenericType(this Type targetType, Type genericType) + { + if (targetType is null) + throw new ArgumentNullException(nameof(targetType)); - if (genericType is null) - throw new ArgumentNullException(nameof(genericType)); + if (genericType is null) + throw new ArgumentNullException(nameof(genericType)); - if (!genericType.IsGenericTypeDefinition) - throw new ArgumentException(nameof(genericType)); + if (!genericType.IsGenericTypeDefinition) + throw new ArgumentException(nameof(genericType)); - if (targetType.IsGenericType && targetType.GetGenericTypeDefinition() == genericType) - return targetType; - else + if (targetType.IsGenericType && targetType.GetGenericTypeDefinition() == genericType) + return targetType; + else + { + if (genericType.IsInterface) { - if (genericType.IsInterface) - { - var findedInterfaces = targetType.GetInterfaces() - .Where(@interface => @interface.IsGenericType && @interface.GetGenericTypeDefinition() == genericType) - .ToList(); - - if (findedInterfaces.Count > 1) - throw new AmbiguousMatchException("Too many interfaces were found."); - else if (findedInterfaces.Count == 1) - return findedInterfaces[0]; - else - return null; - } + var findedInterfaces = targetType.GetInterfaces() + .Where(@interface => @interface.IsGenericType && @interface.GetGenericTypeDefinition() == genericType) + .ToList(); + + if (findedInterfaces.Count > 1) + throw new AmbiguousMatchException("Too many interfaces were found."); + else if (findedInterfaces.Count == 1) + return findedInterfaces[0]; else - { - return targetType.BaseType != null ? GetGenericType(targetType.BaseType, genericType) : null; - } + return null; + } + else + { + return targetType.BaseType != null ? GetGenericType(targetType.BaseType, genericType) : null; } } + } - /// - /// Represents the newline character "\n". - /// - public const string N = "\n"; - - /// - /// Represents the carriage return character "\r". - /// - public const string R = "\r"; - - /// - /// Represents the carriage return and newline characters "\r\n". - /// - public const string RN = "\r\n"; - - /// - /// Splits the string by line separators (RN, R, or N). - /// - /// The string to split. - /// If true, removes empty entries from the result. - /// An array of substrings. - public static string[] SplitByLineSeps(this string str, bool removeEmptyEntries = true) - // https://stackoverflow.com/a/1547483/8029915 - => str.Split( - new[] { RN, R, N }, - removeEmptyEntries ? StringSplitOptions.RemoveEmptyEntries : StringSplitOptions.None - ); - - /// - /// Splits the string using the carriage return as the separator. - /// - /// The string to split. - /// If true, removes empty entries. - /// An array of substrings. - public static string[] SplitByR(this string str, bool removeEmptyEntries = true) - => str.SplitBySep(R, removeEmptyEntries); - - /// - /// Splits the string using the carriage return and newline as the separator. - /// - /// The string to split. - /// If true, removes empty entries. - /// An array of substrings. - public static string[] SplitByRN(this string str, bool removeEmptyEntries = true) - => str.SplitBySep(RN, removeEmptyEntries); - - /// - /// Splits the string using the newline character as the separator. - /// - /// The string to split. - /// If true, removes empty entries. - /// An array of substrings. - public static string[] SplitByN(this string str, bool removeEmptyEntries = true) - => str.SplitBySep(N, removeEmptyEntries); - - /// - /// Splits the string by Environment.NewLine. - /// - /// The string to split. - /// If true, removes empty entries. - /// An array of substrings. - [Obsolete("Use SplitByRN or SplitByN methods.")] - public static string[] SplitLines(this string str, bool removeEmptyEntries = true) - { - return str.SplitBySep(Environment.NewLine, removeEmptyEntries); - } + /// + /// Represents the newline character "\n". + /// + public const string N = "\n"; - /// - /// Splits the string by the specified separator. - /// - /// The string to split. - /// The separator string. - /// If true, removes empty entries. - /// An array of substrings. - [Obsolete("Use SplitBySep method.")] - public static string[] Split(this string str, string separator, bool removeEmptyEntries = true) - { - return str.SplitBySep(separator, removeEmptyEntries); - } + /// + /// Represents the carriage return character "\r". + /// + public const string R = "\r"; - /// - /// Splits the string by the specified separator. - /// - /// The string to split. - /// The separator string. - /// If true, removes empty entries. - /// An array of substrings. - public static string[] SplitBySep(this string str, string separator, bool removeEmptyEntries = true) - { - if (str is null) - throw new ArgumentNullException(nameof(str)); + /// + /// Represents the carriage return and newline characters "\r\n". + /// + public const string RN = "\r\n"; - if (str.Length == 0) - return []; + /// + /// Splits the string by line separators (RN, R, or N). + /// + /// The string to split. + /// If true, removes empty entries from the result. + /// An array of substrings. + public static string[] SplitByLineSeps(this string str, bool removeEmptyEntries = true) + // https://stackoverflow.com/a/1547483/8029915 + => str.Split( + new[] { RN, R, N }, + removeEmptyEntries ? StringSplitOptions.RemoveEmptyEntries : StringSplitOptions.None + ); - return str.Split(new[] { separator }, removeEmptyEntries ? StringSplitOptions.RemoveEmptyEntries : StringSplitOptions.None); - } + /// + /// Splits the string using the carriage return as the separator. + /// + /// The string to split. + /// If true, removes empty entries. + /// An array of substrings. + public static string[] SplitByR(this string str, bool removeEmptyEntries = true) + => str.SplitBySep(R, removeEmptyEntries); - /// - /// Splits the string using a comma as the separator. - /// - /// The string to split. - /// If true, removes empty entries. - /// An array of substrings. - public static string[] SplitByComma(this string str, bool removeEmptyEntries = false) - => str.SplitBySep(",", removeEmptyEntries); - - /// - /// Splits the string using a dot as the separator. - /// - /// The string to split. - /// If true, removes empty entries. - /// An array of substrings. - public static string[] SplitByDot(this string str, bool removeEmptyEntries = false) - => str.SplitBySep(".", removeEmptyEntries); - - /// - /// Splits the string using a semicolon as the separator. - /// - /// The string to split. - /// If true, removes empty entries. - /// An array of substrings. - public static string[] SplitByDotComma(this string str, bool removeEmptyEntries = false) - => str.SplitBySep(";", removeEmptyEntries); - - /// - /// Splits the string using a colon as the separator. - /// - /// The string to split. - /// If true, removes empty entries. - /// An array of substrings. - public static string[] SplitByColon(this string str, bool removeEmptyEntries = true) - => str.SplitBySep(":", removeEmptyEntries); - - /// - /// Splits the string using a space as the separator. - /// - /// The string to split. - /// If true, removes empty entries. - /// An array of substrings. - public static string[] SplitBySpace(this string str, bool removeEmptyEntries = true) - => str.SplitBySep(" ", removeEmptyEntries); - - /// - /// Splits the string using the equal sign as the separator. - /// - /// The string to split. - /// If true, removes empty entries. - /// An array of substrings. - public static string[] SplitByEqual(this string str, bool removeEmptyEntries = true) - => str.SplitBySep("=", removeEmptyEntries); - - /// - /// Splits the string using a tab character as the separator. - /// - /// The string to split. - /// If true, removes empty entries. - /// An array of substrings. - public static string[] SplitByTab(this string str, bool removeEmptyEntries = true) - => str.SplitBySep("\t", removeEmptyEntries); - - /// - /// Splits the string using the "@" symbol as the separator. - /// - /// The string to split. - /// If true, removes empty entries. - /// An array of substrings. - public static string[] SplitByAt(this string str, bool removeEmptyEntries = true) - => str.SplitBySep("@", removeEmptyEntries); - - /// - /// Splits the string using newline as the separator. - /// - /// The string to split. - /// If true, removes empty entries. - /// An array of substrings. - [Obsolete("Use SplitByN methods.")] - public static string[] SplitByLine(this string str, bool removeEmptyEntries = false) - => str.SplitByN(removeEmptyEntries); - - /// - /// Finds the last index of a specified character in a StringBuilder. - /// - /// The StringBuilder to search. - /// The character to locate. - /// The zero-based index position of the character if found; otherwise, -1. - public static int LastIndexOf(this StringBuilder builder, char value) - { - if (builder is null) - throw new ArgumentNullException(nameof(builder)); + /// + /// Splits the string using the carriage return and newline as the separator. + /// + /// The string to split. + /// If true, removes empty entries. + /// An array of substrings. + public static string[] SplitByRN(this string str, bool removeEmptyEntries = true) + => str.SplitBySep(RN, removeEmptyEntries); - for (var i = builder.Length - 1; i > 0; i--) - { - if (builder[i] == value) - return i; - } + /// + /// Splits the string using the newline character as the separator. + /// + /// The string to split. + /// If true, removes empty entries. + /// An array of substrings. + public static string[] SplitByN(this string str, bool removeEmptyEntries = true) + => str.SplitBySep(N, removeEmptyEntries); - return -1; - } + /// + /// Splits the string by Environment.NewLine. + /// + /// The string to split. + /// If true, removes empty entries. + /// An array of substrings. + [Obsolete("Use SplitByRN or SplitByN methods.")] + public static string[] SplitLines(this string str, bool removeEmptyEntries = true) + { + return str.SplitBySep(Environment.NewLine, removeEmptyEntries); + } - /// - /// Determines whether the specified string is a valid email address. - /// - /// The email address to test. - /// True if the email address is valid; otherwise, false. - public static bool IsValidEmailAddress(this string email) - { - return new Regex(@"^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$").IsMatch(email); - } + /// + /// Splits the string by the specified separator. + /// + /// The string to split. + /// The separator string. + /// If true, removes empty entries. + /// An array of substrings. + [Obsolete("Use SplitBySep method.")] + public static string[] Split(this string str, string separator, bool removeEmptyEntries = true) + { + return str.SplitBySep(separator, removeEmptyEntries); + } - /// - /// Determines whether the specified string is a valid URL. - /// - /// The URL to test. - /// True if the URL is valid; otherwise, false. - public static bool IsValidUrl(this string url) - { - const string strRegex = "^(https?://)" - + "?(([0-9a-z_!~*'().&=+$%-]+: )?[0-9a-z_!~*'().&=+$%-]+@)?" //user@ - + @"(([0-9]{1,3}\.){3}[0-9]{1,3}" // IP- 199.194.52.184 - + "|" // allows either IP or domain - + @"([0-9a-z_!~*'()-]+\.)*" // tertiary domain(s)- www. - + @"([0-9a-z][0-9a-z-]{0,61})?[0-9a-z]" // second level domain - + @"(\.[a-z]{2,6})?)" // first level domain- .com or .museum is optional - + "(:[0-9]{1,5})?" // port number- :80 - + "((/?)|" // a slash isn't required if there is no file name - + "(/[0-9a-z_!~*'().;?:@&=+$,%#-]+)+/?)$"; - return new Regex(strRegex).IsMatch(url); - } + /// + /// Splits the string by the specified separator. + /// + /// The string to split. + /// The separator string. + /// If true, removes empty entries. + /// An array of substrings. + public static string[] SplitBySep(this string str, string separator, bool removeEmptyEntries = true) + { + if (str is null) + throw new ArgumentNullException(nameof(str)); - /// - /// Reverses the specified string. - /// - /// The string to reverse. - /// The reversed string. - public static string Reverse(this string input) - { - var chars = input.ToCharArray(); - Array.Reverse(chars); - return new string(chars); - } + if (str.Length == 0) + return []; - /// - /// Reduces the string to a specified length and appends the specified ending. - /// - /// The string to reduce. - /// The total length of the returned string including the ending. - /// The ending to append. - /// A reduced version of the string. - public static string Reduce(this string s, int count, string endings) - { - if (endings.IsEmpty()) - throw new ArgumentNullException(nameof(endings)); + return str.Split(new[] { separator }, removeEmptyEntries ? StringSplitOptions.RemoveEmptyEntries : StringSplitOptions.None); + } - if (count < endings.Length || count >= endings.Length) - throw new ArgumentOutOfRangeException(nameof(count)); + /// + /// Splits the string using a comma as the separator. + /// + /// The string to split. + /// If true, removes empty entries. + /// An array of substrings. + public static string[] SplitByComma(this string str, bool removeEmptyEntries = false) + => str.SplitBySep(",", removeEmptyEntries); - return s.Substring(0, count - endings.Length) + endings; - } + /// + /// Splits the string using a dot as the separator. + /// + /// The string to split. + /// If true, removes empty entries. + /// An array of substrings. + public static string[] SplitByDot(this string str, bool removeEmptyEntries = false) + => str.SplitBySep(".", removeEmptyEntries); - /// - /// Replaces all white space characters in the string with a single space. - /// - /// The string in which to replace white spaces. - /// The modified string. - public static string ReplaceWhiteSpaces(this string s) - { - return s.ReplaceWhiteSpaces(' '); - } + /// + /// Splits the string using a semicolon as the separator. + /// + /// The string to split. + /// If true, removes empty entries. + /// An array of substrings. + public static string[] SplitByDotComma(this string str, bool removeEmptyEntries = false) + => str.SplitBySep(";", removeEmptyEntries); - /// - /// Replaces all white space characters in the string with the specified character. - /// - /// The string in which to replace white spaces. - /// The character to replace white spaces with. - /// The modified string. - public static string ReplaceWhiteSpaces(this string s, char c) - { - if (s is null) - return null; + /// + /// Splits the string using a colon as the separator. + /// + /// The string to split. + /// If true, removes empty entries. + /// An array of substrings. + public static string[] SplitByColon(this string str, bool removeEmptyEntries = true) + => str.SplitBySep(":", removeEmptyEntries); - var sb = new StringBuilder(s); + /// + /// Splits the string using a space as the separator. + /// + /// The string to split. + /// If true, removes empty entries. + /// An array of substrings. + public static string[] SplitBySpace(this string str, bool removeEmptyEntries = true) + => str.SplitBySep(" ", removeEmptyEntries); - for (var i = 0; i < sb.Length; i++) - { - if (char.IsWhiteSpace(sb[i])) - sb[i] = c; - } + /// + /// Splits the string using the equal sign as the separator. + /// + /// The string to split. + /// If true, removes empty entries. + /// An array of substrings. + public static string[] SplitByEqual(this string str, bool removeEmptyEntries = true) + => str.SplitBySep("=", removeEmptyEntries); - return sb.ToString(); - } + /// + /// Splits the string using a tab character as the separator. + /// + /// The string to split. + /// If true, removes empty entries. + /// An array of substrings. + public static string[] SplitByTab(this string str, bool removeEmptyEntries = true) + => str.SplitBySep("\t", removeEmptyEntries); - /// - /// Removes all spaces from the string. - /// - /// The string from which to remove spaces. - /// The string without spaces. - public static string RemoveSpaces(this string s) - { - return s.Remove(" "); - } + /// + /// Splits the string using the "@" symbol as the separator. + /// + /// The string to split. + /// If true, removes empty entries. + /// An array of substrings. + public static string[] SplitByAt(this string str, bool removeEmptyEntries = true) + => str.SplitBySep("@", removeEmptyEntries); - /// - /// Removes all occurrences of the specified substring from the string. - /// - /// The string from which to remove. - /// The substring to remove. - /// If true, performs a case-insensitive removal. - /// The modified string. - public static string Remove(this string s, string what, bool ignoreCase = false) - { - if (ignoreCase) - return s.ReplaceIgnoreCase(what, string.Empty); - else - return s.Replace(what, string.Empty); - } + /// + /// Splits the string using newline as the separator. + /// + /// The string to split. + /// If true, removes empty entries. + /// An array of substrings. + [Obsolete("Use SplitByN methods.")] + public static string[] SplitByLine(this string str, bool removeEmptyEntries = false) + => str.SplitByN(removeEmptyEntries); - /// - /// Determines whether the string represents a number. - /// - /// The string to test. - /// If true, considers floating point numbers; otherwise, integers. - /// True if the string can be parsed as a number; otherwise, false. - public static bool IsNumber(this string s, bool floatPoint) - { - var withoutWhiteSpace = s.RemoveSpaces(); + /// + /// Finds the last index of a specified character in a StringBuilder. + /// + /// The StringBuilder to search. + /// The character to locate. + /// The zero-based index position of the character if found; otherwise, -1. + public static int LastIndexOf(this StringBuilder builder, char value) + { + if (builder is null) + throw new ArgumentNullException(nameof(builder)); - if (floatPoint) - { - return double.TryParse(withoutWhiteSpace, NumberStyles.Any, CultureInfo.InvariantCulture, out _); - } - else - { - return int.TryParse(withoutWhiteSpace, out _); - } + for (var i = builder.Length - 1; i > 0; i--) + { + if (builder[i] == value) + return i; } - /// - /// Determines whether the string contains only numeric characters (and optionally a decimal separator). - /// - /// The string to test. - /// If true, allows decimal points or commas. - /// True if the string contains only numbers; otherwise, false. - public static bool IsNumberOnly(this string s, bool floatPoint) - { - s = s.RemoveSpaces(); + return -1; + } - if (s.Length == 0) - return false; + /// + /// Determines whether the specified string is a valid email address. + /// + /// The email address to test. + /// True if the email address is valid; otherwise, false. + public static bool IsValidEmailAddress(this string email) + { + return new Regex(@"^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$").IsMatch(email); + } - foreach (var c in s) - { - if (c.IsDigit()) - continue; + /// + /// Determines whether the specified string is a valid URL. + /// + /// The URL to test. + /// True if the URL is valid; otherwise, false. + public static bool IsValidUrl(this string url) + { + const string strRegex = "^(https?://)" + + "?(([0-9a-z_!~*'().&=+$%-]+: )?[0-9a-z_!~*'().&=+$%-]+@)?" //user@ + + @"(([0-9]{1,3}\.){3}[0-9]{1,3}" // IP- 199.194.52.184 + + "|" // allows either IP or domain + + @"([0-9a-z_!~*'()-]+\.)*" // tertiary domain(s)- www. + + @"([0-9a-z][0-9a-z-]{0,61})?[0-9a-z]" // second level domain + + @"(\.[a-z]{2,6})?)" // first level domain- .com or .museum is optional + + "(:[0-9]{1,5})?" // port number- :80 + + "((/?)|" // a slash isn't required if there is no file name + + "(/[0-9a-z_!~*'().;?:@&=+$,%#-]+)+/?)$"; + return new Regex(strRegex).IsMatch(url); + } - if (floatPoint && (c == '.' || c == ',')) - continue; + /// + /// Reverses the specified string. + /// + /// The string to reverse. + /// The reversed string. + public static string Reverse(this string input) + { + var chars = input.ToCharArray(); + Array.Reverse(chars); + return new string(chars); + } - return false; - } + /// + /// Reduces the string to a specified length and appends the specified ending. + /// + /// The string to reduce. + /// The total length of the returned string including the ending. + /// The ending to append. + /// A reduced version of the string. + public static string Reduce(this string s, int count, string endings) + { + if (endings.IsEmpty()) + throw new ArgumentNullException(nameof(endings)); - return true; - } + if (count < endings.Length || count >= endings.Length) + throw new ArgumentOutOfRangeException(nameof(count)); - /// - /// Determines whether the specified character is a digit. - /// - /// The character to test. - /// True if the character is a digit; otherwise, false. - public static bool IsDigit(this char c) - => char.IsDigit(c); - - /// - /// Removes diacritical marks from the string. - /// - /// The string from which to remove diacritics. - /// The modified string without accents. - public static string RemoveDiacritics(this string s) - { - var stFormD = s.Normalize(NormalizationForm.FormD); - var sb = new StringBuilder(); + return s.Substring(0, count - endings.Length) + endings; + } - foreach (var t in from t in stFormD let uc = CharUnicodeInfo.GetUnicodeCategory(t) where uc != UnicodeCategory.NonSpacingMark select t) - { - sb.Append(t); - } + /// + /// Replaces all white space characters in the string with a single space. + /// + /// The string in which to replace white spaces. + /// The modified string. + public static string ReplaceWhiteSpaces(this string s) + { + return s.ReplaceWhiteSpaces(' '); + } - return sb.ToString().Normalize(NormalizationForm.FormC); - } + /// + /// Replaces all white space characters in the string with the specified character. + /// + /// The string in which to replace white spaces. + /// The character to replace white spaces with. + /// The modified string. + public static string ReplaceWhiteSpaces(this string s, char c) + { + if (s is null) + return null; - /// - /// Replaces newline characters in the string with HTML line breaks. - /// - /// The string to modify. - /// The modified string with HTML <br /> tags in place of newlines. - public static string Nl2Br(this string s) + var sb = new StringBuilder(s); + + for (var i = 0; i < sb.Length; i++) { - return s.Replace(RN, "
").Replace(N, "
"); + if (char.IsWhiteSpace(sb[i])) + sb[i] = c; } - /// - /// Trims the string to the specified maximum length and appends "..." if it exceeds that length. - /// - /// The string to trim. - /// The maximum length of the returned string. - /// A trimmed version of the string. - public static string Trim(this string value, int maxLength) + return sb.ToString(); + } + + /// + /// Removes all spaces from the string. + /// + /// The string from which to remove spaces. + /// The string without spaces. + public static string RemoveSpaces(this string s) + { + return s.Remove(" "); + } + + /// + /// Removes all occurrences of the specified substring from the string. + /// + /// The string from which to remove. + /// The substring to remove. + /// If true, performs a case-insensitive removal. + /// The modified string. + public static string Remove(this string s, string what, bool ignoreCase = false) + { + if (ignoreCase) + return s.ReplaceIgnoreCase(what, string.Empty); + else + return s.Replace(what, string.Empty); + } + + /// + /// Determines whether the string represents a number. + /// + /// The string to test. + /// If true, considers floating point numbers; otherwise, integers. + /// True if the string can be parsed as a number; otherwise, false. + public static bool IsNumber(this string s, bool floatPoint) + { + var withoutWhiteSpace = s.RemoveSpaces(); + + if (floatPoint) { - if (value != null && value.Length > maxLength) - return value.Substring(0, maxLength) + "..."; - else - return value; + return double.TryParse(withoutWhiteSpace, NumberStyles.Any, CultureInfo.InvariantCulture, out _); } - - /// - /// Joins the collection of strings using "@" as the separator. - /// - /// The collection of strings to join. - /// A string resulting from the join. - public static string JoinAt(this IEnumerable parts) - => parts.Join("@"); - - /// - /// Joins the collection of strings using a tab character as the separator. - /// - /// The collection of strings to join. - /// A string resulting from the join. - public static string JoinTab(this IEnumerable parts) - => parts.Join("\t"); - - /// - /// Joins the collection of strings using a comma as the separator. - /// - /// The collection of strings to join. - /// A string resulting from the join. - public static string JoinComma(this IEnumerable parts) - => parts.Join(","); - - /// - /// Joins the collection of strings using a semicolon as the separator. - /// - /// The collection of strings to join. - /// A string resulting from the join. - public static string JoinDotComma(this IEnumerable parts) - => parts.Join(";"); - - /// - /// Joins the collection of strings using a dot as the separator. - /// - /// The collection of strings to join. - /// A string resulting from the join. - public static string JoinDot(this IEnumerable parts) - => parts.Join("."); - - /// - /// Joins the collection of strings using a comma and a space as the separator. - /// - /// The collection of strings to join. - /// A string resulting from the join. - public static string JoinCommaSpace(this IEnumerable parts) - => parts.Join(", "); - - /// - /// Joins the collection of strings using a space as the separator. - /// - /// The collection of strings to join. - /// A string resulting from the join. - public static string JoinSpace(this IEnumerable parts) - => parts.Join(" "); - - /// - /// Joins the collection of strings using the pipe character as the separator. - /// - /// The collection of strings to join. - /// A string resulting from the join. - public static string JoinPipe(this IEnumerable parts) - => parts.Join("|"); - - /// - /// Joins the collection of strings using a colon as the separator. - /// - /// The collection of strings to join. - /// A string resulting from the join. - public static string JoinColon(this IEnumerable parts) - => parts.Join(":"); - - /// - /// Joins the collection of strings using an equal sign as the separator. - /// - /// The collection of strings to join. - /// A string resulting from the join. - public static string JoinEqual(this IEnumerable parts) - => parts.Join("="); - - /// - /// Joins the collection of strings using the ampersand as the separator. - /// - /// The collection of strings to join. - /// A string resulting from the join. - public static string JoinAnd(this IEnumerable parts) - => parts.Join("&"); - - /// - /// Joins the collection of strings using the newline character as the separator. - /// - /// The collection of strings to join. - /// A string resulting from the join. - public static string JoinN(this IEnumerable parts) - => parts.Join(N); - - /// - /// Joins the collection of strings using the carriage return and newline as the separator. - /// - /// The collection of strings to join. - /// A string resulting from the join. - public static string JoinRN(this IEnumerable parts) - => parts.Join(RN); - - /// - /// Joins the collection of strings using the system's newline as the separator. - /// - /// The collection of strings to join. - /// A string resulting from the join. - public static string JoinNL(this IEnumerable parts) - => parts.Join(Environment.NewLine); - - /// - /// Joins the collection of strings using the specified separator. - /// - /// The collection of strings to join. - /// The separator string. - /// A string resulting from the join. - public static string Join(this IEnumerable parts, string separator) + else { - return string.Join(separator, [.. parts]); + return int.TryParse(withoutWhiteSpace, out _); } + } + + /// + /// Determines whether the string contains only numeric characters (and optionally a decimal separator). + /// + /// The string to test. + /// If true, allows decimal points or commas. + /// True if the string contains only numbers; otherwise, false. + public static bool IsNumberOnly(this string s, bool floatPoint) + { + s = s.RemoveSpaces(); + + if (s.Length == 0) + return false; - /// - /// Determines whether two strings are equal, ignoring case. - /// - /// The first string to compare. - /// The second string to compare. - /// True if the strings are equal ignoring case; otherwise, false. - public static bool EqualsIgnoreCase(this string str1, string str2) + foreach (var c in s) { - return string.Equals(str1, str2, StringComparison.InvariantCultureIgnoreCase); + if (c.IsDigit()) + continue; + + if (floatPoint && (c == '.' || c == ',')) + continue; + + return false; } - /// - /// Compares two strings for equality, ignoring case. - /// - /// The first string to compare. - /// The second string to compare. - /// True if the strings are equal ignoring case; otherwise, false. - [Obsolete("Use EqualsIgnoreCase.")] - public static bool CompareIgnoreCase(this string str1, string str2) + return true; + } + + /// + /// Determines whether the specified character is a digit. + /// + /// The character to test. + /// True if the character is a digit; otherwise, false. + public static bool IsDigit(this char c) + => char.IsDigit(c); + + /// + /// Removes diacritical marks from the string. + /// + /// The string from which to remove diacritics. + /// The modified string without accents. + public static string RemoveDiacritics(this string s) + { + var stFormD = s.Normalize(NormalizationForm.FormD); + var sb = new StringBuilder(); + + foreach (var t in from t in stFormD let uc = CharUnicodeInfo.GetUnicodeCategory(t) where uc != UnicodeCategory.NonSpacingMark select t) { - return string.Compare(str1, str2, StringComparison.InvariantCultureIgnoreCase) == 0; + sb.Append(t); } - /// - /// Determines whether the first string contains the second string, ignoring case. - /// - /// The string to search. - /// The string to locate. - /// True if str1 contains str2 when ignoring case; otherwise, false. - public static bool ContainsIgnoreCase(this string str1, string str2) + return sb.ToString().Normalize(NormalizationForm.FormC); + } + + /// + /// Replaces newline characters in the string with HTML line breaks. + /// + /// The string to modify. + /// The modified string with HTML <br /> tags in place of newlines. + public static string Nl2Br(this string s) + { + return s.Replace(RN, "
").Replace(N, "
"); + } + + /// + /// Trims the string to the specified maximum length and appends "..." if it exceeds that length. + /// + /// The string to trim. + /// The maximum length of the returned string. + /// A trimmed version of the string. + public static string Trim(this string value, int maxLength) + { + if (value != null && value.Length > maxLength) + return value.Substring(0, maxLength) + "..."; + else + return value; + } + + /// + /// Joins the collection of strings using "@" as the separator. + /// + /// The collection of strings to join. + /// A string resulting from the join. + public static string JoinAt(this IEnumerable parts) + => parts.Join("@"); + + /// + /// Joins the collection of strings using a tab character as the separator. + /// + /// The collection of strings to join. + /// A string resulting from the join. + public static string JoinTab(this IEnumerable parts) + => parts.Join("\t"); + + /// + /// Joins the collection of strings using a comma as the separator. + /// + /// The collection of strings to join. + /// A string resulting from the join. + public static string JoinComma(this IEnumerable parts) + => parts.Join(","); + + /// + /// Joins the collection of strings using a semicolon as the separator. + /// + /// The collection of strings to join. + /// A string resulting from the join. + public static string JoinDotComma(this IEnumerable parts) + => parts.Join(";"); + + /// + /// Joins the collection of strings using a dot as the separator. + /// + /// The collection of strings to join. + /// A string resulting from the join. + public static string JoinDot(this IEnumerable parts) + => parts.Join("."); + + /// + /// Joins the collection of strings using a comma and a space as the separator. + /// + /// The collection of strings to join. + /// A string resulting from the join. + public static string JoinCommaSpace(this IEnumerable parts) + => parts.Join(", "); + + /// + /// Joins the collection of strings using a space as the separator. + /// + /// The collection of strings to join. + /// A string resulting from the join. + public static string JoinSpace(this IEnumerable parts) + => parts.Join(" "); + + /// + /// Joins the collection of strings using the pipe character as the separator. + /// + /// The collection of strings to join. + /// A string resulting from the join. + public static string JoinPipe(this IEnumerable parts) + => parts.Join("|"); + + /// + /// Joins the collection of strings using a colon as the separator. + /// + /// The collection of strings to join. + /// A string resulting from the join. + public static string JoinColon(this IEnumerable parts) + => parts.Join(":"); + + /// + /// Joins the collection of strings using an equal sign as the separator. + /// + /// The collection of strings to join. + /// A string resulting from the join. + public static string JoinEqual(this IEnumerable parts) + => parts.Join("="); + + /// + /// Joins the collection of strings using the ampersand as the separator. + /// + /// The collection of strings to join. + /// A string resulting from the join. + public static string JoinAnd(this IEnumerable parts) + => parts.Join("&"); + + /// + /// Joins the collection of strings using the newline character as the separator. + /// + /// The collection of strings to join. + /// A string resulting from the join. + public static string JoinN(this IEnumerable parts) + => parts.Join(N); + + /// + /// Joins the collection of strings using the carriage return and newline as the separator. + /// + /// The collection of strings to join. + /// A string resulting from the join. + public static string JoinRN(this IEnumerable parts) + => parts.Join(RN); + + /// + /// Joins the collection of strings using the system's newline as the separator. + /// + /// The collection of strings to join. + /// A string resulting from the join. + public static string JoinNL(this IEnumerable parts) + => parts.Join(Environment.NewLine); + + /// + /// Joins the collection of strings using the specified separator. + /// + /// The collection of strings to join. + /// The separator string. + /// A string resulting from the join. + public static string Join(this IEnumerable parts, string separator) + { + return string.Join(separator, [.. parts]); + } + + /// + /// Determines whether two strings are equal, ignoring case. + /// + /// The first string to compare. + /// The second string to compare. + /// True if the strings are equal ignoring case; otherwise, false. + public static bool EqualsIgnoreCase(this string str1, string str2) + { + return string.Equals(str1, str2, StringComparison.InvariantCultureIgnoreCase); + } + + /// + /// Compares two strings for equality, ignoring case. + /// + /// The first string to compare. + /// The second string to compare. + /// True if the strings are equal ignoring case; otherwise, false. + [Obsolete("Use EqualsIgnoreCase.")] + public static bool CompareIgnoreCase(this string str1, string str2) + { + return string.Compare(str1, str2, StringComparison.InvariantCultureIgnoreCase) == 0; + } + + /// + /// Determines whether the first string contains the second string, ignoring case. + /// + /// The string to search. + /// The string to locate. + /// True if str1 contains str2 when ignoring case; otherwise, false. + public static bool ContainsIgnoreCase(this string str1, string str2) + { + if (str1 is null) { - if (str1 is null) - { - return false; - //throw new ArgumentNullException(nameof(str1)); - } + return false; + //throw new ArgumentNullException(nameof(str1)); + } - if (str2 is null) - return false; + if (str2 is null) + return false; #if NETSTANDARD2_0 - return str1.IndexOf(str2, StringComparison.InvariantCultureIgnoreCase) >= 0; + return str1.IndexOf(str2, StringComparison.InvariantCultureIgnoreCase) >= 0; #else - return str1.Contains(str2, StringComparison.InvariantCultureIgnoreCase); + return str1.Contains(str2, StringComparison.InvariantCultureIgnoreCase); #endif - } + } - /// - /// Replaces occurrences of a specified substring with another string, ignoring case. - /// - /// The original string. - /// The substring to replace. - /// The replacement string. - /// The modified string. - public static string ReplaceIgnoreCase(this string original, string oldValue, string newValue) - { - if (oldValue is null) - throw new ArgumentNullException(nameof(oldValue)); + /// + /// Replaces occurrences of a specified substring with another string, ignoring case. + /// + /// The original string. + /// The substring to replace. + /// The replacement string. + /// The modified string. + public static string ReplaceIgnoreCase(this string original, string oldValue, string newValue) + { + if (oldValue is null) + throw new ArgumentNullException(nameof(oldValue)); - if (newValue is null) - throw new ArgumentNullException(nameof(newValue)); + if (newValue is null) + throw new ArgumentNullException(nameof(newValue)); - if (original is null) - { - return null; - //throw new ArgumentNullException(nameof(original)); - } + if (original is null) + { + return null; + //throw new ArgumentNullException(nameof(original)); + } - if (oldValue.Length == 0) - return original.IsEmpty() ? newValue : original; + if (oldValue.Length == 0) + return original.IsEmpty() ? newValue : original; #if NETSTANDARD2_0 - return Regex.Replace(original, oldValue, newValue, RegexOptions.IgnoreCase); + return Regex.Replace(original, oldValue, newValue, RegexOptions.IgnoreCase); #else - return original.Replace(oldValue, newValue, StringComparison.InvariantCultureIgnoreCase); + return original.Replace(oldValue, newValue, StringComparison.InvariantCultureIgnoreCase); #endif - } + } - /// - /// Replaces occurrences of a specified substring with another string in a StringBuilder, ignoring case. - /// - /// The StringBuilder to modify. - /// The substring to replace. - /// The replacement string. - /// The modified StringBuilder. - public static StringBuilder ReplaceIgnoreCase(this StringBuilder builder, string oldValue, string newValue) - { - if (builder is null) - throw new ArgumentNullException(nameof(builder)); + /// + /// Replaces occurrences of a specified substring with another string in a StringBuilder, ignoring case. + /// + /// The StringBuilder to modify. + /// The substring to replace. + /// The replacement string. + /// The modified StringBuilder. + public static StringBuilder ReplaceIgnoreCase(this StringBuilder builder, string oldValue, string newValue) + { + if (builder is null) + throw new ArgumentNullException(nameof(builder)); - var str = builder.ToString().ReplaceIgnoreCase(oldValue, newValue); - return builder - .Clear() - .Append(str); - } + var str = builder.ToString().ReplaceIgnoreCase(oldValue, newValue); + return builder + .Clear() + .Append(str); + } - /// - /// Determines whether the string starts with the specified substring, ignoring case. - /// - /// The string to test. - /// The substring to compare. - /// True if str1 starts with str2 ignoring case; otherwise, false. - public static bool StartsWithIgnoreCase(this string str1, string str2) + /// + /// Determines whether the string starts with the specified substring, ignoring case. + /// + /// The string to test. + /// The substring to compare. + /// True if str1 starts with str2 ignoring case; otherwise, false. + public static bool StartsWithIgnoreCase(this string str1, string str2) + { + if (str1 is null) { - if (str1 is null) - { - return false; - //throw new ArgumentNullException(nameof(str1)); - } + return false; + //throw new ArgumentNullException(nameof(str1)); + } - if (str2 is null) - return false; + if (str2 is null) + return false; - return str1.StartsWith(str2, StringComparison.InvariantCultureIgnoreCase); - } + return str1.StartsWith(str2, StringComparison.InvariantCultureIgnoreCase); + } - /// - /// Determines whether the string ends with the specified substring, ignoring case. - /// - /// The string to test. - /// The substring to compare. - /// True if str1 ends with str2 ignoring case; otherwise, false. - public static bool EndsWithIgnoreCase(this string str1, string str2) + /// + /// Determines whether the string ends with the specified substring, ignoring case. + /// + /// The string to test. + /// The substring to compare. + /// True if str1 ends with str2 ignoring case; otherwise, false. + public static bool EndsWithIgnoreCase(this string str1, string str2) + { + if (str1 is null) { - if (str1 is null) - { - return false; - //throw new ArgumentNullException(nameof(str1)); - } + return false; + //throw new ArgumentNullException(nameof(str1)); + } - if (str2 is null) - return false; + if (str2 is null) + return false; - return str1.EndsWith(str2, StringComparison.InvariantCultureIgnoreCase); - } + return str1.EndsWith(str2, StringComparison.InvariantCultureIgnoreCase); + } - /// - /// Returns the zero-based index of the first occurrence of the specified substring, ignoring case. - /// - /// The string to search. - /// The substring to locate. - /// The starting index for the search. Defaults to -1 for beginning. - /// The index of the first occurrence, or -1 if not found. - public static int IndexOfIgnoreCase(this string str1, string str2, int index = -1) + /// + /// Returns the zero-based index of the first occurrence of the specified substring, ignoring case. + /// + /// The string to search. + /// The substring to locate. + /// The starting index for the search. Defaults to -1 for beginning. + /// The index of the first occurrence, or -1 if not found. + public static int IndexOfIgnoreCase(this string str1, string str2, int index = -1) + { + if (str1 is null) { - if (str1 is null) - { - return -1; - //throw new ArgumentNullException(nameof(str1)); - } + return -1; + //throw new ArgumentNullException(nameof(str1)); + } - if (str2 is null) - return -1; + if (str2 is null) + return -1; - if (index == -1) - return str1.IndexOf(str2, StringComparison.InvariantCultureIgnoreCase); - else - return str1.IndexOf(str2, index, StringComparison.InvariantCultureIgnoreCase); - } + if (index == -1) + return str1.IndexOf(str2, StringComparison.InvariantCultureIgnoreCase); + else + return str1.IndexOf(str2, index, StringComparison.InvariantCultureIgnoreCase); + } - /// - /// Searches for the last occurrence of a specified string, ignoring case. - /// - /// The string to search within. - /// The string to search for. - /// The starting position of the search. The search is conducted from this position to the beginning. Default is -1 (entire string). - /// The index of the last occurrence of str2 in str1, or -1 if not found or if either string is null. - public static int LastIndexOfIgnoreCase(this string str1, string str2, int index = -1) + /// + /// Searches for the last occurrence of a specified string, ignoring case. + /// + /// The string to search within. + /// The string to search for. + /// The starting position of the search. The search is conducted from this position to the beginning. Default is -1 (entire string). + /// The index of the last occurrence of str2 in str1, or -1 if not found or if either string is null. + public static int LastIndexOfIgnoreCase(this string str1, string str2, int index = -1) + { + if (str1 is null) { - if (str1 is null) - { - return -1; - //throw new ArgumentNullException(nameof(str1)); - } + return -1; + //throw new ArgumentNullException(nameof(str1)); + } - if (str2 is null) - return -1; + if (str2 is null) + return -1; - if (index == -1) - return str1.LastIndexOf(str2, StringComparison.InvariantCultureIgnoreCase); - else - return str1.LastIndexOf(str2, index, StringComparison.InvariantCultureIgnoreCase); - } + if (index == -1) + return str1.LastIndexOf(str2, StringComparison.InvariantCultureIgnoreCase); + else + return str1.LastIndexOf(str2, index, StringComparison.InvariantCultureIgnoreCase); + } - // - // http://ppetrov.wordpress.com/2008/06/30/useful-method-8-of-n-string-capitalize-firsttotitlecase/ - // + // + // http://ppetrov.wordpress.com/2008/06/30/useful-method-8-of-n-string-capitalize-firsttotitlecase/ + // - /// - /// Converts the string to title case using the current culture. - /// - /// The string to convert. - /// The string converted to title case. - public static string ToTitleCase(this string value) - { - var ti = Thread.CurrentThread.CurrentCulture.TextInfo; - return ti.ToTitleCase(value); - } + /// + /// Converts the string to title case using the current culture. + /// + /// The string to convert. + /// The string converted to title case. + public static string ToTitleCase(this string value) + { + var ti = Thread.CurrentThread.CurrentCulture.TextInfo; + return ti.ToTitleCase(value); + } - // - // http://ppetrov.wordpress.com/2008/06/13/useful-method-1-of-n/ - // - - /// - /// Repeats the string a specified number of times. - /// - /// The string to repeat. - /// The number of times to repeat the string. - /// A new string containing the original string repeated n times. - public static string Times(this string value, int n) - { - return value.Times(n, string.Empty); - } + // + // http://ppetrov.wordpress.com/2008/06/13/useful-method-1-of-n/ + // - /// - /// Repeats the string a specified number of times with a separator between each repetition. - /// - /// The string to repeat. - /// The number of times to repeat the string. - /// The string to use as a separator between repetitions. - /// A new string containing the original string repeated n times with the specified separator. - /// Thrown when value is null. - /// Thrown when n is less than 1. - public static string Times(this string value, int n, string separator) - { - if (value is null) - throw new ArgumentNullException(nameof(value)); + /// + /// Repeats the string a specified number of times. + /// + /// The string to repeat. + /// The number of times to repeat the string. + /// A new string containing the original string repeated n times. + public static string Times(this string value, int n) + { + return value.Times(n, string.Empty); + } - if (n < 1) - throw new ArgumentOutOfRangeException(nameof(n), n, "Must be a positive number."); + /// + /// Repeats the string a specified number of times with a separator between each repetition. + /// + /// The string to repeat. + /// The number of times to repeat the string. + /// The string to use as a separator between repetitions. + /// A new string containing the original string repeated n times with the specified separator. + /// Thrown when value is null. + /// Thrown when n is less than 1. + public static string Times(this string value, int n, string separator) + { + if (value is null) + throw new ArgumentNullException(nameof(value)); - if (value.Length > 0 && n > 0) - return Enumerable.Repeat(value, n).Join(separator); + if (n < 1) + throw new ArgumentOutOfRangeException(nameof(n), n, "Must be a positive number."); - return value; - } + if (value.Length > 0 && n > 0) + return Enumerable.Repeat(value, n).Join(separator); + + return value; + } + + // + // http://www.extensionmethod.net/Details.aspx?ID=123 + // + + /// + /// Truncates the string to a specified length and appends "..." if truncated. + /// + /// The string to truncate. + /// The maximum length of the resulting string. + /// The truncated string. + public static string Truncate(this string text, int maxLength) + { + return text.Truncate(maxLength, "..."); + } + + /// + /// Truncates the string to a specified length and appends a custom suffix if truncated. + /// + /// The string to truncate. + /// The maximum length of the resulting string before adding the suffix. + /// The string to append if truncation occurs. + /// The truncated string with the suffix if truncation occurred. + /// Thrown when maxLength is negative. + public static string Truncate(this string text, int maxLength, string suffix) + { + if (maxLength < 0) + throw new ArgumentOutOfRangeException(nameof(maxLength), nameof(maxLength), "maxLength is negative."); + else if (maxLength == 0) + return suffix; + else if (maxLength >= text.Length) + return text; + else + return text.Substring(0, maxLength) + suffix; + } + + /// + /// Truncates a string in the middle, preserving the start and end portions while adding an ellipsis in between. + /// + /// The string to truncate. + /// The maximum length of the resulting string including the ellipsis. + /// The truncated string with ellipsis in the middle if truncation was necessary, otherwise the original string. + public static string TruncateMiddle(this string input, int limit) + { + if (input.IsEmpty()) + return input; + + var output = input; + const string middle = "..."; + + // Check if the string is longer than the allowed amount + // otherwise do nothing + if (output.Length <= limit || limit <= 0) + return output; - // - // http://www.extensionmethod.net/Details.aspx?ID=123 - // - - /// - /// Truncates the string to a specified length and appends "..." if truncated. - /// - /// The string to truncate. - /// The maximum length of the resulting string. - /// The truncated string. - public static string Truncate(this string text, int maxLength) + // figure out how much to make it fit... + var left = (limit / 2) - (middle.Length / 2); + var right = limit - left - (middle.Length / 2); + + if ((left + right + middle.Length) < limit) { - return text.Truncate(maxLength, "..."); + right++; } - - /// - /// Truncates the string to a specified length and appends a custom suffix if truncated. - /// - /// The string to truncate. - /// The maximum length of the resulting string before adding the suffix. - /// The string to append if truncation occurs. - /// The truncated string with the suffix if truncation occurred. - /// Thrown when maxLength is negative. - public static string Truncate(this string text, int maxLength, string suffix) + else if ((left + right + middle.Length) > limit) { - if (maxLength < 0) - throw new ArgumentOutOfRangeException(nameof(maxLength), nameof(maxLength), "maxLength is negative."); - else if (maxLength == 0) - return suffix; - else if (maxLength >= text.Length) - return text; - else - return text.Substring(0, maxLength) + suffix; + right--; } - /// - /// Truncates a string in the middle, preserving the start and end portions while adding an ellipsis in between. - /// - /// The string to truncate. - /// The maximum length of the resulting string including the ellipsis. - /// The truncated string with ellipsis in the middle if truncation was necessary, otherwise the original string. - public static string TruncateMiddle(this string input, int limit) - { - if (input.IsEmpty()) - return input; + // cut the left side + output = input.Substring(0, left); - var output = input; - const string middle = "..."; + // add the middle + output += middle; - // Check if the string is longer than the allowed amount - // otherwise do nothing - if (output.Length <= limit || limit <= 0) - return output; + // add the right side... + output += input.Substring(input.Length - right, right); - // figure out how much to make it fit... - var left = (limit / 2) - (middle.Length / 2); - var right = limit - left - (middle.Length / 2); + return output; + } - if ((left + right + middle.Length) < limit) - { - right++; - } - else if ((left + right + middle.Length) > limit) - { - right--; - } + /// + /// Removes trailing zeros from the string using the current thread's number format decimal separator. + /// + /// The input string. + /// A string with trailing zeros removed. + public static string RemoveTrailingZeros(this string s) + { + return s.RemoveTrailingZeros(Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator); + } - // cut the left side - output = input.Substring(0, left); + /// + /// Removes trailing zeros from the string based on the specified separator. + /// + /// The input string. + /// The separator to consider while removing zeros. + /// A string with trailing zeros removed. + public static string RemoveTrailingZeros(this string s, string separator) + { + if (s.IsEmpty()) + throw new ArgumentNullException(nameof(s)); - // add the middle - output += middle; + if (separator.IsEmpty()) + throw new ArgumentNullException(nameof(separator)); - // add the right side... - output += input.Substring(input.Length - right, right); + s = s.TrimStart('0').TrimEnd('0'); - return output; - } + //var index = s.IndexOf(separator); + + //if (index == -1) + // index = s.Length; + + //var endIndex = 0; + + //for (var i = 0; i < index; i++) + //{ + // if (s[i] == '0') + // endIndex = i + 1; + // else + // break; + //} - /// - /// Removes trailing zeros from the string using the current thread's number format decimal separator. - /// - /// The input string. - /// A string with trailing zeros removed. - public static string RemoveTrailingZeros(this string s) + //if (endIndex > 0) + // s = s.Substring(endIndex); + + //for (var i = s.Length - 1; i > index; i--) + //{ + // if (s[i] == '0') + // endIndex = i - 1; + // else + // break; + //} + + //s = s.TrimStart('0').TrimEnd('0', separator[0]); + + if (s.StartsWith(separator)) + s = "0" + s; + + if (s.EndsWith(separator)) + s = s.Substring(0, s.Length - 1); + + if (s.IsEmpty()) + s = "0"; + + return s; + } + + /// + /// Converts a base64 encoded string to a byte array. + /// + /// The base64 encoded string. + /// The byte array representation. + public static byte[] Base64(this string value) + { + return Convert.FromBase64String(value); + } + + /// + /// Converts a byte array to a base64 encoded string. + /// + /// The byte array. + /// The base64 encoded string. + public static string Base64(this byte[] value) + { + return Convert.ToBase64String(value); + } + + /// + /// Splits the specified string into substrings of the given length. + /// + /// The string to split. + /// The maximum length of each substring. + /// An enumerable collection of substrings. + public static IEnumerable SplitByLength(this string stringToSplit, int length) + { + while (stringToSplit.Length > length) { - return s.RemoveTrailingZeros(Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator); + yield return stringToSplit.Substring(0, length); + stringToSplit = stringToSplit.Substring(length); } - /// - /// Removes trailing zeros from the string based on the specified separator. - /// - /// The input string. - /// The separator to consider while removing zeros. - /// A string with trailing zeros removed. - public static string RemoveTrailingZeros(this string s, string separator) - { - if (s.IsEmpty()) - throw new ArgumentNullException(nameof(s)); + if (stringToSplit.Length > 0) + yield return stringToSplit; + } - if (separator.IsEmpty()) - throw new ArgumentNullException(nameof(separator)); + /// + /// Trims the specified start value from the beginning of the string if present. + /// + /// The input string. + /// The starting substring to remove. + /// The trimmed string. + public static string TrimStart(this string str, string sStartValue) + { + return str.StartsWith(sStartValue) ? str.Remove(0, sStartValue.Length) : str; + } - s = s.TrimStart('0').TrimEnd('0'); + /// + /// Trims the specified end value from the end of the string if present. + /// + /// The input string. + /// The ending substring to remove. + /// The trimmed string. + public static string TrimEnd(this string str, string sEndValue) + { + return str.EndsWith(sEndValue) ? str.Remove(str.Length - sEndValue.Length, sEndValue.Length) : str; + } + + /// + /// Checks whether the string starts with the specified start string and ends with the specified end string. + /// + /// The input string. + /// The starting string. + /// The ending string. + /// true if the string is enclosed by the specified brackets; otherwise, false. + public static bool CheckBrackets(this string str, string sStart, string sEnd) + { + return str.StartsWith(sStart) && str.EndsWith(sEnd); + } + + /// + /// Removes the enclosing brackets from the string if present. + /// + /// The input string. + /// The starting bracket string. + /// The ending bracket string. + /// The string with brackets removed, if they were present. + public static string StripBrackets(this string str, string sStart, string sEnd) + { + return str.CheckBrackets(sStart, sEnd) + ? str.Substring(sStart.Length, (str.Length - sStart.Length) - sEnd.Length) + : str; + } + + private static readonly Dictionary _charMap = new() + { + { 'а', "a" }, + { 'б', "b" }, + { 'в', "v" }, + { 'г', "g" }, + { 'д', "d" }, + { 'е', "e" }, + { 'ё', "yo" }, + { 'ж', "zh" }, + { 'з', "z" }, + { 'и', "i" }, + { 'й', "i" }, + { 'к', "k" }, + { 'л', "l" }, + { 'м', "m" }, + { 'н', "n" }, + { 'о', "o" }, + { 'п', "p" }, + { 'р', "r" }, + { 'с', "s" }, + { 'т', "t" }, + { 'у', "u" }, + { 'ф', "f" }, + { 'х', "h" }, + { 'ц', "ts" }, + { 'ч', "ch" }, + { 'ш', "sh" }, + { 'щ', "shsh" }, + { 'ы', "y" }, + { 'э', "eh" }, + { 'ю', "yu" }, + { 'я', "ya" }, + { 'ь', "'" }, + { 'ъ', "'" }, + }; + + /// + /// Transliterates the Russian title to Latin characters. + /// + /// The Russian string. + /// The transliterated string in Latin. + public static string ToLatin(this string russianTitle) + { + if (russianTitle.IsEmpty()) + return russianTitle; + + var transliter = string.Empty; - //var index = s.IndexOf(separator); + foreach (var letter in russianTitle.ToLower()) + { + if (_charMap.TryGetValue(letter, out var mappedLetter)) + transliter += mappedLetter; + else + transliter += letter; + } - //if (index == -1) - // index = s.Length; + return transliter; + } + + /// + /// Performs light screening on the input text by replacing spaces with hyphens and removing certain characters. + /// + /// The input text. + /// The screened text. + public static string LightScreening(this string text) + => text?.Replace(' ', '-').Remove(".").Remove("#").Remove("?").Remove(":"); + + /// + /// Compares two file paths for equality after normalizing them. + /// + /// The first file path. + /// The second file path. + /// true if the paths are equal; otherwise, false. + public static bool ComparePaths(this string path1, string path2) + { + // http://stackoverflow.com/questions/2281531/how-can-i-compare-directory-paths-in-c + return Path.GetFullPath(path1).TrimEnd('\\').EqualsIgnoreCase(Path.GetFullPath(path2).TrimEnd('\\')); + } - //var endIndex = 0; + /// + /// Determines if the current string matches the specified pattern using SQL-like wildcards. + /// + /// The string to search in. + /// The pattern to search for. Use '_' for single character and '%' for multiple characters. + /// if set to true, the comparison ignores case. + /// true if the string matches the pattern; otherwise, false. + public static bool Like(this string toSearch, string toFind, bool ignoreCase = true) + { + var option = RegexOptions.Singleline; - //for (var i = 0; i < index; i++) - //{ - // if (s[i] == '0') - // endIndex = i + 1; - // else - // break; - //} + if (ignoreCase) + option = RegexOptions.IgnoreCase; - //if (endIndex > 0) - // s = s.Substring(endIndex); + return new Regex(@"\A" + new Regex(@"\.|\$|\^|\{|\[|\(|\||\)|\*|\+|\?|\\").Replace(toFind, ch => @"\" + ch).Replace('_', '.').Replace("%", ".*") + @"\z", option).IsMatch(toSearch); + } - //for (var i = s.Length - 1; i > index; i--) - //{ - // if (s[i] == '0') - // endIndex = i - 1; - // else - // break; - //} + /// + /// Determines whether the specified secure string is empty. + /// + /// The secure string to check. + /// true if the secure string is null or empty; otherwise, false. + public static bool IsEmpty(this SecureString secureString) + => secureString is null || secureString.Length == 0; - //s = s.TrimStart('0').TrimEnd('0', separator[0]); + /// + /// Throws an ArgumentNullException if the secure string is empty. + /// + /// The secure string. + /// The name of the parameter. + /// The original secure string if not empty. + public static SecureString ThrowIfEmpty(this SecureString str, string paramName) + => str.IsEmpty() ? throw new ArgumentNullException(paramName) : str; - if (s.StartsWith(separator)) - s = "0" + s; + /// + /// Determines whether two secure strings are equal. + /// + /// The first secure string. + /// The second secure string. + /// true if both secure strings are equal; otherwise, false. + public static bool IsEqualTo(this SecureString value1, SecureString value2) + { + if (value1 is null) + return value2 is null; - if (s.EndsWith(separator)) - s = s.Substring(0, s.Length - 1); + if (value2 is null) + return false; - if (s.IsEmpty()) - s = "0"; + if (value1.Length != value2.Length) + return false; - return s; - } + var bstr1 = IntPtr.Zero; + var bstr2 = IntPtr.Zero; - /// - /// Converts a base64 encoded string to a byte array. - /// - /// The base64 encoded string. - /// The byte array representation. - public static byte[] Base64(this string value) + try { - return Convert.FromBase64String(value); - } + bstr1 = Marshal.SecureStringToBSTR(value1); + bstr2 = Marshal.SecureStringToBSTR(value2); - /// - /// Converts a byte array to a base64 encoded string. - /// - /// The byte array. - /// The base64 encoded string. - public static string Base64(this byte[] value) - { - return Convert.ToBase64String(value); - } + var length = Marshal.ReadInt32(bstr1, -4); - /// - /// Splits the specified string into substrings of the given length. - /// - /// The string to split. - /// The maximum length of each substring. - /// An enumerable collection of substrings. - public static IEnumerable SplitByLength(this string stringToSplit, int length) - { - while (stringToSplit.Length > length) + for (var x = 0; x < length; ++x) { - yield return stringToSplit.Substring(0, length); - stringToSplit = stringToSplit.Substring(length); - } - - if (stringToSplit.Length > 0) - yield return stringToSplit; - } + var byte1 = Marshal.ReadByte(bstr1, x); + var byte2 = Marshal.ReadByte(bstr2, x); - /// - /// Trims the specified start value from the beginning of the string if present. - /// - /// The input string. - /// The starting substring to remove. - /// The trimmed string. - public static string TrimStart(this string str, string sStartValue) - { - return str.StartsWith(sStartValue) ? str.Remove(0, sStartValue.Length) : str; - } + if (byte1 != byte2) + return false; + } - /// - /// Trims the specified end value from the end of the string if present. - /// - /// The input string. - /// The ending substring to remove. - /// The trimmed string. - public static string TrimEnd(this string str, string sEndValue) - { - return str.EndsWith(sEndValue) ? str.Remove(str.Length - sEndValue.Length, sEndValue.Length) : str; + return true; } - - /// - /// Checks whether the string starts with the specified start string and ends with the specified end string. - /// - /// The input string. - /// The starting string. - /// The ending string. - /// true if the string is enclosed by the specified brackets; otherwise, false. - public static bool CheckBrackets(this string str, string sStart, string sEnd) + finally { - return str.StartsWith(sStart) && str.EndsWith(sEnd); - } + if (bstr2 != IntPtr.Zero) + Marshal.ZeroFreeBSTR(bstr2); - /// - /// Removes the enclosing brackets from the string if present. - /// - /// The input string. - /// The starting bracket string. - /// The ending bracket string. - /// The string with brackets removed, if they were present. - public static string StripBrackets(this string str, string sStart, string sEnd) - { - return str.CheckBrackets(sStart, sEnd) - ? str.Substring(sStart.Length, (str.Length - sStart.Length) - sEnd.Length) - : str; + if (bstr1 != IntPtr.Zero) + Marshal.ZeroFreeBSTR(bstr1); } + } - private static readonly Dictionary _charMap = new() - { - { 'а', "a" }, - { 'б', "b" }, - { 'в', "v" }, - { 'г', "g" }, - { 'д', "d" }, - { 'е', "e" }, - { 'ё', "yo" }, - { 'ж', "zh" }, - { 'з', "z" }, - { 'и', "i" }, - { 'й', "i" }, - { 'к', "k" }, - { 'л', "l" }, - { 'м', "m" }, - { 'н', "n" }, - { 'о', "o" }, - { 'п', "p" }, - { 'р', "r" }, - { 'с', "s" }, - { 'т', "t" }, - { 'у', "u" }, - { 'ф', "f" }, - { 'х', "h" }, - { 'ц', "ts" }, - { 'ч', "ch" }, - { 'ш', "sh" }, - { 'щ', "shsh" }, - { 'ы', "y" }, - { 'э', "eh" }, - { 'ю', "yu" }, - { 'я', "ya" }, - { 'ь', "'" }, - { 'ъ', "'" }, - }; - - /// - /// Transliterates the Russian title to Latin characters. - /// - /// The Russian string. - /// The transliterated string in Latin. - public static string ToLatin(this string russianTitle) - { - if (russianTitle.IsEmpty()) - return russianTitle; - - var transliter = string.Empty; - - foreach (var letter in russianTitle.ToLower()) - { - if (_charMap.TryGetValue(letter, out var mappedLetter)) - transliter += mappedLetter; - else - transliter += letter; - } + /// + /// Converts the byte array digest to its hexadecimal string representation. + /// + /// The byte array digest. + /// The hexadecimal string. + public static string Digest(this byte[] digest) + { + return digest.Digest(digest.Length); + } - return transliter; - } + /// + /// Converts a portion of the byte array digest to its hexadecimal string representation. + /// + /// The byte array digest. + /// The number of bytes to process. + /// The starting index in the array. + /// The hexadecimal string. + public static string Digest(this byte[] digest, int? length, int index = 0) + { + return BitConverter.ToString(digest, index, length ?? digest.Length).Remove("-"); + } - /// - /// Performs light screening on the input text by replacing spaces with hyphens and removing certain characters. - /// - /// The input text. - /// The screened text. - public static string LightScreening(this string text) - => text?.Replace(' ', '-').Remove(".").Remove("#").Remove("?").Remove(":"); - - /// - /// Compares two file paths for equality after normalizing them. - /// - /// The first file path. - /// The second file path. - /// true if the paths are equal; otherwise, false. - public static bool ComparePaths(this string path1, string path2) - { - // http://stackoverflow.com/questions/2281531/how-can-i-compare-directory-paths-in-c - return Path.GetFullPath(path1).TrimEnd('\\').EqualsIgnoreCase(Path.GetFullPath(path2).TrimEnd('\\')); - } + //private static readonly byte[] _initVectorBytes = Encoding.ASCII.GetBytes("ss14fgty650h8u82"); + //private const int _keysize = 256; + + //public static string Encrypt(this string data, string password) + //{ + // using (var pwd = new Rfc2898DeriveBytes(password, _initVectorBytes)) + // { + // var keyBytes = pwd.GetBytes(_keysize / 8); + + // using (var symmetricKey = new RijndaelManaged { Mode = CipherMode.CBC }) + // using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, _initVectorBytes)) + // using (var memoryStream = new MemoryStream()) + // using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write)) + // { + // var bytes = data.UTF8(); + + // cryptoStream.Write(bytes, 0, bytes.Length); + // cryptoStream.FlushFinalBlock(); + + // return memoryStream.ToArray().Base64(); + // } + // } + //} + + //public static string Decrypt(this string data, string password) + //{ + // using (var pwd = new Rfc2898DeriveBytes(password, _initVectorBytes)) + // { + // var cipherTextBytes = data.Base64(); + // var keyBytes = pwd.GetBytes(_keysize / 8); + + // using (var symmetricKey = new RijndaelManaged { Mode = CipherMode.CBC }) + // using (var decryptor = symmetricKey.CreateDecryptor(keyBytes, _initVectorBytes)) + // using (var memoryStream = new MemoryStream(cipherTextBytes)) + // using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read)) + // { + // var plainTextBytes = new byte[cipherTextBytes.Length]; + // var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length); + // return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount); + // } + // } + //} - /// - /// Determines if the current string matches the specified pattern using SQL-like wildcards. - /// - /// The string to search in. - /// The pattern to search for. Use '_' for single character and '%' for multiple characters. - /// if set to true, the comparison ignores case. - /// true if the string matches the pattern; otherwise, false. - public static bool Like(this string toSearch, string toFind, bool ignoreCase = true) - { - var option = RegexOptions.Singleline; + /// + /// Gets the Windows Cyrillic encoding (code page 1251). + /// + public static Encoding WindowsCyrillic => Encoding.GetEncoding(1251); - if (ignoreCase) - option = RegexOptions.IgnoreCase; + /// + /// Provides hexadecimal encoding functionality. + /// + public static readonly HexEncoding HexEncoding = new(); - return new Regex(@"\A" + new Regex(@"\.|\$|\^|\{|\[|\(|\||\)|\*|\+|\?|\\").Replace(toFind, ch => @"\" + ch).Replace('_', '.').Replace("%", ".*") + @"\z", option).IsMatch(toSearch); - } + /// + /// Returns the duplicate strings from the provided collection, ignoring case. + /// + /// The collection of strings. + /// An enumerable collection of duplicate strings. + public static IEnumerable Duplicates(this IEnumerable items) + => items.GroupBy(s => s, s => StringComparer.InvariantCultureIgnoreCase).Where(g => g.Count() > 1).Select(g => g.Key); - /// - /// Determines whether the specified secure string is empty. - /// - /// The secure string to check. - /// true if the secure string is null or empty; otherwise, false. - public static bool IsEmpty(this SecureString secureString) - => secureString is null || secureString.Length == 0; - - /// - /// Throws an ArgumentNullException if the secure string is empty. - /// - /// The secure string. - /// The name of the parameter. - /// The original secure string if not empty. - public static SecureString ThrowIfEmpty(this SecureString str, string paramName) - => str.IsEmpty() ? throw new ArgumentNullException(paramName) : str; - - /// - /// Determines whether two secure strings are equal. - /// - /// The first secure string. - /// The second secure string. - /// true if both secure strings are equal; otherwise, false. - public static bool IsEqualTo(this SecureString value1, SecureString value2) - { - if (value1 is null) - return value2 is null; + /// + /// Gets the default encoding bytes for the string. + /// + /// The input string. + /// The byte array in default encoding. + public static byte[] Default(this string v) => Encoding.Default.GetBytes(v); + /// + /// Gets the string from a byte array using the default encoding. + /// + /// The byte array. + /// The string decoded from the byte array. + public static string Default(this byte[] v) => Default(v, 0, v.Length); + /// + /// Gets the string from a portion of the byte array using the default encoding. + /// + /// The byte array. + /// The starting index. + /// The number of bytes to decode. + /// The decoded string. + public static string Default(this byte[] v, int index, int count) => Encoding.Default.GetString(v, index, count); + /// + /// Gets the string from an ArraySegment using the default encoding. + /// + /// The ArraySegment of bytes. + /// The decoded string. + public static string Default(this ArraySegment v) => v.Array.Default(v.Offset, v.Count); + /// + /// Gets the string from a byte array using the default encoding with a specified count. + /// + /// The byte array. + /// The number of bytes to decode. + /// The starting index. + /// The decoded string. + [CLSCompliant(false)] + public static string Default(this byte[] v, uint count, int index = 0) => Default(v, index, (int)count); - if (value2 is null) - return false; + /// + /// Gets the ASCII encoded bytes for the string. + /// + /// The input string. + /// The ASCII byte array. + public static byte[] ASCII(this string v) => Encoding.ASCII.GetBytes(v); + /// + /// Gets the string from a byte array using ASCII encoding. + /// + /// The byte array. + /// The ASCII decoded string. + public static string ASCII(this byte[] v) => ASCII(v, 0, v.Length); + /// + /// Gets the string from a portion of the byte array using ASCII encoding. + /// + /// The byte array. + /// The starting index. + /// The number of bytes to decode. + /// The decoded string. + public static string ASCII(this byte[] v, int index, int count) => Encoding.ASCII.GetString(v, index, count); + /// + /// Gets the string from an ArraySegment using ASCII encoding. + /// + /// The ArraySegment of bytes. + /// The decoded string. + public static string ASCII(this ArraySegment v) => v.Array.ASCII(v.Offset, v.Count); + /// + /// Gets the string from a byte array using ASCII encoding with a specified count. + /// + /// The byte array. + /// The number of bytes to decode. + /// The starting index. + /// The decoded string. + [CLSCompliant(false)] + public static string ASCII(this byte[] v, uint count, int index = 0) => ASCII(v, index, (int)count); - if (value1.Length != value2.Length) - return false; + /// + /// Gets the UTF8 encoded bytes for the string. + /// + /// The input string. + /// The UTF8 byte array. + public static byte[] UTF8(this string v) => Encoding.UTF8.GetBytes(v); + /// + /// Gets the string from a byte array using UTF8 encoding. + /// + /// The byte array. + /// The UTF8 decoded string. + public static string UTF8(this byte[] v) => UTF8(v, 0, v.Length); + /// + /// Gets the string from a portion of the byte array using UTF8 encoding. + /// + /// The byte array. + /// The starting index. + /// The number of bytes to decode. + /// The decoded string. + public static string UTF8(this byte[] v, int index, int count) => Encoding.UTF8.GetString(v, index, count); + /// + /// Gets the string from an ArraySegment using UTF8 encoding. + /// + /// The ArraySegment of bytes. + /// The decoded string. + public static string UTF8(this ArraySegment v) => v.Array.UTF8(v.Offset, v.Count); + /// + /// Gets the string from a byte array using UTF8 encoding with a specified count. + /// + /// The byte array. + /// The number of bytes to decode. + /// The starting index. + /// The decoded string. + [CLSCompliant(false)] + public static string UTF8(this byte[] v, uint count, int index = 0) => UTF8(v, index, (int)count); - var bstr1 = IntPtr.Zero; - var bstr2 = IntPtr.Zero; + /// + /// Gets the Unicode encoded bytes for the string. + /// + /// The input string. + /// The Unicode byte array. + public static byte[] Unicode(this string v) => Encoding.Unicode.GetBytes(v); + /// + /// Gets the string from a byte array using Unicode encoding. + /// + /// The byte array. + /// The Unicode decoded string. + public static string Unicode(this byte[] v) => Unicode(v, 0, v.Length); + /// + /// Gets the string from a portion of the byte array using Unicode encoding. + /// + /// The byte array. + /// The starting index. + /// The number of bytes to decode. + /// The decoded string. + public static string Unicode(this byte[] v, int index, int count) => Encoding.Unicode.GetString(v, index, count); + /// + /// Gets the string from an ArraySegment using Unicode encoding. + /// + /// The ArraySegment of bytes. + /// The decoded string. + public static string Unicode(this ArraySegment v) => v.Array.Unicode(v.Offset, v.Count); + /// + /// Gets the string from a byte array using Unicode encoding with a specified count. + /// + /// The byte array. + /// The number of bytes to decode. + /// The starting index. + /// The decoded string. + [CLSCompliant(false)] + public static string Unicode(this byte[] v, uint count, int index = 0) => Unicode(v, index, (int)count); - try - { - bstr1 = Marshal.SecureStringToBSTR(value1); - bstr2 = Marshal.SecureStringToBSTR(value2); + /// + /// Gets the Windows Cyrillic encoded bytes for the string. + /// + /// The input string. + /// The Windows Cyrillic byte array. + public static byte[] Cyrillic(this string v) => WindowsCyrillic.GetBytes(v); + /// + /// Gets the string from a byte array using Windows Cyrillic encoding. + /// + /// The byte array. + /// The decoded string. + public static string Cyrillic(this byte[] v) => Cyrillic(v, 0, v.Length); + /// + /// Gets the string from a portion of the byte array using Windows Cyrillic encoding. + /// + /// The byte array. + /// The starting index. + /// The number of bytes to decode. + /// The decoded string. + public static string Cyrillic(this byte[] v, int index, int count) => WindowsCyrillic.GetString(v, index, count); + /// + /// Gets the string from an ArraySegment using Windows Cyrillic encoding. + /// + /// The ArraySegment of bytes. + /// The decoded string. + public static string Cyrillic(this ArraySegment v) => v.Array.Cyrillic(v.Offset, v.Count); + /// + /// Gets the string from a byte array using Windows Cyrillic encoding with a specified count. + /// + /// The byte array. + /// The number of bytes to decode. + /// The starting index. + /// The decoded string. + [CLSCompliant(false)] + public static string Cyrillic(this byte[] v, uint count, int index = 0) => Cyrillic(v, index, (int)count); - var length = Marshal.ReadInt32(bstr1, -4); + /// + /// Gets the hexadecimal encoded bytes for the string. + /// + /// The input string. + /// The hexadecimal byte array. + public static byte[] Hex(this string v) => HexEncoding.GetBytes(v); + /// + /// Gets the string from a byte array using hexadecimal encoding. + /// + /// The byte array. + /// The hexadecimal decoded string. + public static string Hex(this byte[] v) => Hex(v, 0, v.Length); + /// + /// Gets the string from a portion of the byte array using hexadecimal encoding. + /// + /// The byte array. + /// The starting index. + /// The number of bytes to decode. + /// The decoded hexadecimal string. + public static string Hex(this byte[] v, int index, int count) => HexEncoding.GetString(v, index, count); + /// + /// Gets the string from an ArraySegment using hexadecimal encoding. + /// + /// The ArraySegment of bytes. + /// The decoded hexadecimal string. + public static string Hex(this ArraySegment v) => v.Array.Hex(v.Offset, v.Count); + /// + /// Gets the string from a byte array using hexadecimal encoding with a specified count. + /// + /// The byte array. + /// The number of bytes to decode. + /// The starting index. + /// The decoded hexadecimal string. + [CLSCompliant(false)] + public static string Hex(this byte[] v, uint count, int index = 0) => Hex(v, index, (int)count); - for (var x = 0; x < length; ++x) - { - var byte1 = Marshal.ReadByte(bstr1, x); - var byte2 = Marshal.ReadByte(bstr2, x); + /// + /// Decodes an ArraySegment of bytes into a string using the specified encoding. + /// + /// The encoding to use. + /// The ArraySegment of bytes. + /// The decoded string. + public static string GetString(this Encoding encoding, ArraySegment buffer) + => encoding.CheckOnNull(nameof(encoding)).GetString(buffer.Array, buffer.Offset, buffer.Count); - if (byte1 != byte2) - return false; - } + /// + /// Converts the string to a SecureString. + /// + /// The input string. + /// The SecureString equivalent of the input string. + public static SecureString Secure(this string str) + => str?.ToCharArray().TypedTo(); - return true; - } - finally - { - if (bstr2 != IntPtr.Zero) - Marshal.ZeroFreeBSTR(bstr2); + /// + /// Converts the SecureString to an unsecured string. + /// + /// The SecureString. + /// The unsecured string equivalent. + public static string UnSecure(this SecureString str) + { + if (str is null) + return null; - if (bstr1 != IntPtr.Zero) - Marshal.ZeroFreeBSTR(bstr1); - } - } + var bstr = Marshal.SecureStringToBSTR(str); - /// - /// Converts the byte array digest to its hexadecimal string representation. - /// - /// The byte array digest. - /// The hexadecimal string. - public static string Digest(this byte[] digest) + using (bstr.MakeDisposable(Marshal.ZeroFreeBSTR)) { - return digest.Digest(digest.Length); + return Marshal.PtrToStringBSTR(bstr); } + } - /// - /// Converts a portion of the byte array digest to its hexadecimal string representation. - /// - /// The byte array digest. - /// The number of bytes to process. - /// The starting index in the array. - /// The hexadecimal string. - public static string Digest(this byte[] digest, int? length, int index = 0) - { - return BitConverter.ToString(digest, index, length ?? digest.Length).Remove("-"); - } + /// + /// Converts a SecureString key to a numeric identifier. + /// + /// The secure key. + /// A numeric identifier or null if the key is null. + public static int? ToId(this SecureString key) => key?.UnSecure().GetDeterministicHashCode(); - //private static readonly byte[] _initVectorBytes = Encoding.ASCII.GetBytes("ss14fgty650h8u82"); - //private const int _keysize = 256; + /// + /// Converts a character array to a string using the specified count and starting index. + /// + /// The character array. + /// The number of characters to convert. + /// The starting index in the array. + /// A new string constructed from the array. + [CLSCompliant(false)] + public static string ToString(this char[] arr, uint count, int index = 0) + => arr.ToString((int)count, index); - //public static string Encrypt(this string data, string password) - //{ - // using (var pwd = new Rfc2898DeriveBytes(password, _initVectorBytes)) - // { - // var keyBytes = pwd.GetBytes(_keysize / 8); - - // using (var symmetricKey = new RijndaelManaged { Mode = CipherMode.CBC }) - // using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, _initVectorBytes)) - // using (var memoryStream = new MemoryStream()) - // using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write)) - // { - // var bytes = data.UTF8(); - - // cryptoStream.Write(bytes, 0, bytes.Length); - // cryptoStream.FlushFinalBlock(); - - // return memoryStream.ToArray().Base64(); - // } - // } - //} + /// + /// Converts a character array to a string using the specified count and starting index. + /// + /// The character array. + /// The number of characters to convert. + /// The starting index in the array. + /// A new string constructed from the array. + public static string ToString(this char[] arr, int count, int index = 0) + => count == 0 ? string.Empty : new string(arr, index, count); - //public static string Decrypt(this string data, string password) - //{ - // using (var pwd = new Rfc2898DeriveBytes(password, _initVectorBytes)) - // { - // var cipherTextBytes = data.Base64(); - // var keyBytes = pwd.GetBytes(_keysize / 8); - - // using (var symmetricKey = new RijndaelManaged { Mode = CipherMode.CBC }) - // using (var decryptor = symmetricKey.CreateDecryptor(keyBytes, _initVectorBytes)) - // using (var memoryStream = new MemoryStream(cipherTextBytes)) - // using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read)) - // { - // var plainTextBytes = new byte[cipherTextBytes.Length]; - // var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length); - // return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount); - // } - // } - //} + /// + /// Converts an ArraySegment of bytes to its bit string representation with a specified separator. + /// + /// The byte array segment. + /// The character separator between byte bits. + /// A string representing the bits of the bytes. + public static string ToBitString(this ArraySegment buffer, char separator = ' ') + => buffer.Array.ToBitString(buffer.Offset, buffer.Count, separator); - /// - /// Gets the Windows Cyrillic encoding (code page 1251). - /// - public static Encoding WindowsCyrillic => Encoding.GetEncoding(1251); - - /// - /// Provides hexadecimal encoding functionality. - /// - public static readonly HexEncoding HexEncoding = new(); - - /// - /// Returns the duplicate strings from the provided collection, ignoring case. - /// - /// The collection of strings. - /// An enumerable collection of duplicate strings. - public static IEnumerable Duplicates(this IEnumerable items) - => items.GroupBy(s => s, s => StringComparer.InvariantCultureIgnoreCase).Where(g => g.Count() > 1).Select(g => g.Key); - - /// - /// Gets the default encoding bytes for the string. - /// - /// The input string. - /// The byte array in default encoding. - public static byte[] Default(this string v) => Encoding.Default.GetBytes(v); - /// - /// Gets the string from a byte array using the default encoding. - /// - /// The byte array. - /// The string decoded from the byte array. - public static string Default(this byte[] v) => Default(v, 0, v.Length); - /// - /// Gets the string from a portion of the byte array using the default encoding. - /// - /// The byte array. - /// The starting index. - /// The number of bytes to decode. - /// The decoded string. - public static string Default(this byte[] v, int index, int count) => Encoding.Default.GetString(v, index, count); - /// - /// Gets the string from an ArraySegment using the default encoding. - /// - /// The ArraySegment of bytes. - /// The decoded string. - public static string Default(this ArraySegment v) => v.Array.Default(v.Offset, v.Count); - /// - /// Gets the string from a byte array using the default encoding with a specified count. - /// - /// The byte array. - /// The number of bytes to decode. - /// The starting index. - /// The decoded string. - [CLSCompliant(false)] - public static string Default(this byte[] v, uint count, int index = 0) => Default(v, index, (int)count); - - /// - /// Gets the ASCII encoded bytes for the string. - /// - /// The input string. - /// The ASCII byte array. - public static byte[] ASCII(this string v) => Encoding.ASCII.GetBytes(v); - /// - /// Gets the string from a byte array using ASCII encoding. - /// - /// The byte array. - /// The ASCII decoded string. - public static string ASCII(this byte[] v) => ASCII(v, 0, v.Length); - /// - /// Gets the string from a portion of the byte array using ASCII encoding. - /// - /// The byte array. - /// The starting index. - /// The number of bytes to decode. - /// The decoded string. - public static string ASCII(this byte[] v, int index, int count) => Encoding.ASCII.GetString(v, index, count); - /// - /// Gets the string from an ArraySegment using ASCII encoding. - /// - /// The ArraySegment of bytes. - /// The decoded string. - public static string ASCII(this ArraySegment v) => v.Array.ASCII(v.Offset, v.Count); - /// - /// Gets the string from a byte array using ASCII encoding with a specified count. - /// - /// The byte array. - /// The number of bytes to decode. - /// The starting index. - /// The decoded string. - [CLSCompliant(false)] - public static string ASCII(this byte[] v, uint count, int index = 0) => ASCII(v, index, (int)count); - - /// - /// Gets the UTF8 encoded bytes for the string. - /// - /// The input string. - /// The UTF8 byte array. - public static byte[] UTF8(this string v) => Encoding.UTF8.GetBytes(v); - /// - /// Gets the string from a byte array using UTF8 encoding. - /// - /// The byte array. - /// The UTF8 decoded string. - public static string UTF8(this byte[] v) => UTF8(v, 0, v.Length); - /// - /// Gets the string from a portion of the byte array using UTF8 encoding. - /// - /// The byte array. - /// The starting index. - /// The number of bytes to decode. - /// The decoded string. - public static string UTF8(this byte[] v, int index, int count) => Encoding.UTF8.GetString(v, index, count); - /// - /// Gets the string from an ArraySegment using UTF8 encoding. - /// - /// The ArraySegment of bytes. - /// The decoded string. - public static string UTF8(this ArraySegment v) => v.Array.UTF8(v.Offset, v.Count); - /// - /// Gets the string from a byte array using UTF8 encoding with a specified count. - /// - /// The byte array. - /// The number of bytes to decode. - /// The starting index. - /// The decoded string. - [CLSCompliant(false)] - public static string UTF8(this byte[] v, uint count, int index = 0) => UTF8(v, index, (int)count); - - /// - /// Gets the Unicode encoded bytes for the string. - /// - /// The input string. - /// The Unicode byte array. - public static byte[] Unicode(this string v) => Encoding.Unicode.GetBytes(v); - /// - /// Gets the string from a byte array using Unicode encoding. - /// - /// The byte array. - /// The Unicode decoded string. - public static string Unicode(this byte[] v) => Unicode(v, 0, v.Length); - /// - /// Gets the string from a portion of the byte array using Unicode encoding. - /// - /// The byte array. - /// The starting index. - /// The number of bytes to decode. - /// The decoded string. - public static string Unicode(this byte[] v, int index, int count) => Encoding.Unicode.GetString(v, index, count); - /// - /// Gets the string from an ArraySegment using Unicode encoding. - /// - /// The ArraySegment of bytes. - /// The decoded string. - public static string Unicode(this ArraySegment v) => v.Array.Unicode(v.Offset, v.Count); - /// - /// Gets the string from a byte array using Unicode encoding with a specified count. - /// - /// The byte array. - /// The number of bytes to decode. - /// The starting index. - /// The decoded string. - [CLSCompliant(false)] - public static string Unicode(this byte[] v, uint count, int index = 0) => Unicode(v, index, (int)count); - - /// - /// Gets the Windows Cyrillic encoded bytes for the string. - /// - /// The input string. - /// The Windows Cyrillic byte array. - public static byte[] Cyrillic(this string v) => WindowsCyrillic.GetBytes(v); - /// - /// Gets the string from a byte array using Windows Cyrillic encoding. - /// - /// The byte array. - /// The decoded string. - public static string Cyrillic(this byte[] v) => Cyrillic(v, 0, v.Length); - /// - /// Gets the string from a portion of the byte array using Windows Cyrillic encoding. - /// - /// The byte array. - /// The starting index. - /// The number of bytes to decode. - /// The decoded string. - public static string Cyrillic(this byte[] v, int index, int count) => WindowsCyrillic.GetString(v, index, count); - /// - /// Gets the string from an ArraySegment using Windows Cyrillic encoding. - /// - /// The ArraySegment of bytes. - /// The decoded string. - public static string Cyrillic(this ArraySegment v) => v.Array.Cyrillic(v.Offset, v.Count); - /// - /// Gets the string from a byte array using Windows Cyrillic encoding with a specified count. - /// - /// The byte array. - /// The number of bytes to decode. - /// The starting index. - /// The decoded string. - [CLSCompliant(false)] - public static string Cyrillic(this byte[] v, uint count, int index = 0) => Cyrillic(v, index, (int)count); - - /// - /// Gets the hexadecimal encoded bytes for the string. - /// - /// The input string. - /// The hexadecimal byte array. - public static byte[] Hex(this string v) => HexEncoding.GetBytes(v); - /// - /// Gets the string from a byte array using hexadecimal encoding. - /// - /// The byte array. - /// The hexadecimal decoded string. - public static string Hex(this byte[] v) => Hex(v, 0, v.Length); - /// - /// Gets the string from a portion of the byte array using hexadecimal encoding. - /// - /// The byte array. - /// The starting index. - /// The number of bytes to decode. - /// The decoded hexadecimal string. - public static string Hex(this byte[] v, int index, int count) => HexEncoding.GetString(v, index, count); - /// - /// Gets the string from an ArraySegment using hexadecimal encoding. - /// - /// The ArraySegment of bytes. - /// The decoded hexadecimal string. - public static string Hex(this ArraySegment v) => v.Array.Hex(v.Offset, v.Count); - /// - /// Gets the string from a byte array using hexadecimal encoding with a specified count. - /// - /// The byte array. - /// The number of bytes to decode. - /// The starting index. - /// The decoded hexadecimal string. - [CLSCompliant(false)] - public static string Hex(this byte[] v, uint count, int index = 0) => Hex(v, index, (int)count); - - /// - /// Decodes an ArraySegment of bytes into a string using the specified encoding. - /// - /// The encoding to use. - /// The ArraySegment of bytes. - /// The decoded string. - public static string GetString(this Encoding encoding, ArraySegment buffer) - => encoding.CheckOnNull(nameof(encoding)).GetString(buffer.Array, buffer.Offset, buffer.Count); - - /// - /// Converts the string to a SecureString. - /// - /// The input string. - /// The SecureString equivalent of the input string. - public static SecureString Secure(this string str) - => str?.ToCharArray().TypedTo(); - - /// - /// Converts the SecureString to an unsecured string. - /// - /// The SecureString. - /// The unsecured string equivalent. - public static string UnSecure(this SecureString str) - { - if (str is null) - return null; + /// + /// Converts a byte array to its bit string representation with a specified separator. + /// + /// The byte array. + /// Optional start index for conversion. + /// Optional count of bytes to convert. + /// The character separator between byte bits. + /// A string representing the bits of the bytes. + public static string ToBitString(this byte[] buffer, int? index = null, int? count = null, char separator = ' ') + { + if (buffer is null) + throw new ArgumentNullException(nameof(buffer)); - var bstr = Marshal.SecureStringToBSTR(str); + var offset = index ?? 0; + var len = count ?? buffer.Length; - using (bstr.MakeDisposable(Marshal.ZeroFreeBSTR)) - { - return Marshal.PtrToStringBSTR(bstr); - } - } + if ((offset + len) > buffer.Length) + throw new ArgumentOutOfRangeException(nameof(index)); - /// - /// Converts a SecureString key to a numeric identifier. - /// - /// The secure key. - /// A numeric identifier or null if the key is null. - public static int? ToId(this SecureString key) => key?.UnSecure().GetDeterministicHashCode(); - - /// - /// Converts a character array to a string using the specified count and starting index. - /// - /// The character array. - /// The number of characters to convert. - /// The starting index in the array. - /// A new string constructed from the array. - [CLSCompliant(false)] - public static string ToString(this char[] arr, uint count, int index = 0) - => arr.ToString((int)count, index); - - /// - /// Converts a character array to a string using the specified count and starting index. - /// - /// The character array. - /// The number of characters to convert. - /// The starting index in the array. - /// A new string constructed from the array. - public static string ToString(this char[] arr, int count, int index = 0) - => count == 0 ? string.Empty : new string(arr, index, count); - - /// - /// Converts an ArraySegment of bytes to its bit string representation with a specified separator. - /// - /// The byte array segment. - /// The character separator between byte bits. - /// A string representing the bits of the bytes. - public static string ToBitString(this ArraySegment buffer, char separator = ' ') - => buffer.Array.ToBitString(buffer.Offset, buffer.Count, separator); - - /// - /// Converts a byte array to its bit string representation with a specified separator. - /// - /// The byte array. - /// Optional start index for conversion. - /// Optional count of bytes to convert. - /// The character separator between byte bits. - /// A string representing the bits of the bytes. - public static string ToBitString(this byte[] buffer, int? index = null, int? count = null, char separator = ' ') - { - if (buffer is null) - throw new ArgumentNullException(nameof(buffer)); + if (offset == len) + return string.Empty; - var offset = index ?? 0; - var len = count ?? buffer.Length; + var builder = new StringBuilder(); - if ((offset + len) > buffer.Length) - throw new ArgumentOutOfRangeException(nameof(index)); + for (var i = 0; i < len; i++) + { + var bits = Convert.ToString(buffer[i + offset] & 0xFF, 2).PadLeft(8, '0'); - if (offset == len) - return string.Empty; + builder.Append(bits).Append(separator); + } - var builder = new StringBuilder(); + if (builder.Length > 0) + builder.Remove(builder.Length - 1, 1); - for (var i = 0; i < len; i++) - { - var bits = Convert.ToString(buffer[i + offset] & 0xFF, 2).PadLeft(8, '0'); + return builder.ToString(); + } - builder.Append(bits).Append(separator); - } + /// + /// Converts a bit string representation to a byte array. + /// + /// The string containing bits. + /// The character that separates individual byte bits. + /// A byte array created from the bit string. + public static byte[] ToByteArray(this string bitString, char separator = ' ') + { + if (bitString is null) + throw new ArgumentNullException(nameof(bitString)); - if (builder.Length > 0) - builder.Remove(builder.Length - 1, 1); + if (bitString.Length == 0) + return []; - return builder.ToString(); - } + var bitStrings = bitString.Split(separator); + var bytes = new byte[bitStrings.Length]; - /// - /// Converts a bit string representation to a byte array. - /// - /// The string containing bits. - /// The character that separates individual byte bits. - /// A byte array created from the bit string. - public static byte[] ToByteArray(this string bitString, char separator = ' ') + for (var i = 0; i < bitStrings.Length; i++) { - if (bitString is null) - throw new ArgumentNullException(nameof(bitString)); - - if (bitString.Length == 0) - return []; + bytes[i] = (byte)Convert.ToInt32(bitStrings[i], 2); + } - var bitStrings = bitString.Split(separator); - var bytes = new byte[bitStrings.Length]; + return bytes; + } - for (var i = 0; i < bitStrings.Length; i++) - { - bytes[i] = (byte)Convert.ToInt32(bitStrings[i], 2); - } + /// + /// Calculates a deterministic hash code for the specified string. + /// + /// The string to hash. + /// An integer hash code computed deterministically. + public static unsafe int GetDeterministicHashCode(this string value) + { + if (value is null) + throw new ArgumentNullException(nameof(value)); - return bytes; - } + // decompiled code from .NET FW + // reason - make in stable in FW and CORE + // https://andrewlock.net/why-is-string-gethashcode-different-each-time-i-run-my-program-in-net-core/ - /// - /// Calculates a deterministic hash code for the specified string. - /// - /// The string to hash. - /// An integer hash code computed deterministically. - public static unsafe int GetDeterministicHashCode(this string value) + fixed (char* str = value) { - if (value is null) - throw new ArgumentNullException(nameof(value)); - - // decompiled code from .NET FW - // reason - make in stable in FW and CORE - // https://andrewlock.net/why-is-string-gethashcode-different-each-time-i-run-my-program-in-net-core/ - - fixed (char* str = value) + char* chPtr = str; + + if (Environment.Is64BitProcess) { - char* chPtr = str; - - if (Environment.Is64BitProcess) - { - int hash1 = 5381; + int hash1 = 5381; int hash2 = hash1; int c; while ((c = chPtr[0]) != 0) - { + { hash1 = ((hash1 << 5) + hash1) ^ c; c = chPtr[1]; @@ -1844,215 +1844,214 @@ public static unsafe int GetDeterministicHashCode(this string value) } return hash1 + (hash2 * 1566083941); + } + else + { + int num = 352654597; + int num2 = num; + int* numPtr = (int*)chPtr; + int length = value.Length; + + while (length > 2) + { + num = (((num << 5) + num) + (num >> 27)) ^ numPtr[0]; + num2 = (((num2 << 5) + num2) + (num2 >> 27)) ^ numPtr[1]; + numPtr += 2; + length -= 4; } - else + + if (length > 0) { - int num = 352654597; - int num2 = num; - int* numPtr = (int*)chPtr; - int length = value.Length; - - while (length > 2) - { - num = (((num << 5) + num) + (num >> 27)) ^ numPtr[0]; - num2 = (((num2 << 5) + num2) + (num2 >> 27)) ^ numPtr[1]; - numPtr += 2; - length -= 4; - } - - if (length > 0) - { - num = (((num << 5) + num) + (num >> 27)) ^ numPtr[0]; - } - - return (num + (num2 * 1566083941)); + num = (((num << 5) + num) + (num >> 27)) ^ numPtr[0]; } + + return (num + (num2 * 1566083941)); } } + } + + /// + /// Tries to parse the string to a long integer. + /// + /// The string to parse. + /// The long value if successfully parsed; otherwise, null. + public static long? TryToLong(this string str) + { + return long.TryParse(str, out var l) ? l : (long?)null; + } + + /// + /// Searches for the specified pattern in the source string and returns the zero-based index of its first occurrence. + /// + /// The source string to search. + /// The pattern to locate. + /// The index of the first occurrence; otherwise, -1 if not found. + public static int FastIndexOf(this string source, string pattern) + { + if (source is null) + throw new ArgumentNullException(nameof(source)); + + if (pattern is null) + throw new ArgumentNullException(nameof(pattern)); + + if (pattern.Length == 0) + { + return 0; + } - /// - /// Tries to parse the string to a long integer. - /// - /// The string to parse. - /// The long value if successfully parsed; otherwise, null. - public static long? TryToLong(this string str) + if (pattern.Length == 1) { - return long.TryParse(str, out var l) ? l : (long?)null; + return source.IndexOf(pattern[0]); } - /// - /// Searches for the specified pattern in the source string and returns the zero-based index of its first occurrence. - /// - /// The source string to search. - /// The pattern to locate. - /// The index of the first occurrence; otherwise, -1 if not found. - public static int FastIndexOf(this string source, string pattern) + int limit = source.Length - pattern.Length + 1; + if (limit < 1) { - if (source is null) - throw new ArgumentNullException(nameof(source)); + return -1; + } - if (pattern is null) - throw new ArgumentNullException(nameof(pattern)); + // Store the first 2 characters of the pattern. + char c0 = pattern[0]; + char c1 = pattern[1]; - if (pattern.Length == 0) + // Find the first occurrence of the first character. + int first = source.IndexOf(c0, 0, limit); + while (first != -1) + { + // Check if the following character matches the second character of the pattern. + if (source[first + 1] != c1) { - return 0; + first = source.IndexOf(c0, ++first, limit - first); + continue; } - if (pattern.Length == 1) + // Check the rest of the pattern (starting with the 3rd character). + bool found = true; + for (int j = 2; j < pattern.Length; j++) { - return source.IndexOf(pattern[0]); + if (source[first + j] != pattern[j]) + { + found = false; + break; + } } - int limit = source.Length - pattern.Length + 1; - if (limit < 1) + // If the entire pattern is found, return its index; otherwise, continue searching. + if (found) { - return -1; + return first; } - // Store the first 2 characters of the pattern. - char c0 = pattern[0]; - char c1 = pattern[1]; + first = source.IndexOf(c0, ++first, limit - first); + } - // Find the first occurrence of the first character. - int first = source.IndexOf(c0, 0, limit); - while (first != -1) - { - // Check if the following character matches the second character of the pattern. - if (source[first + 1] != c1) - { - first = source.IndexOf(c0, ++first, limit - first); - continue; - } + return -1; + } - // Check the rest of the pattern (starting with the 3rd character). - bool found = true; - for (int j = 2; j < pattern.Length; j++) - { - if (source[first + j] != pattern[j]) - { - found = false; - break; - } - } + /// + /// Removes multiple consecutive whitespace characters from the specified text. + /// + /// The input text. + /// A string with multiple whitespaces replaced by a single space. + public static string RemoveMultipleWhitespace(this string text) + { + string result = string.Empty; + if (text.IsEmptyOrWhiteSpace()) + { + return result; + } - // If the entire pattern is found, return its index; otherwise, continue searching. - if (found) - { - return first; - } + var r = new Regex(@"\s+"); + return r.Replace(text, @" "); + } - first = source.IndexOf(c0, ++first, limit - first); - } + /// + /// Escapes a URL string. + /// + /// The URL to escape. + /// An escaped URL string. + [Obsolete] + public static string UrlEscape(this string url) + => Uri.EscapeUriString(url); - return -1; - } + /// + /// Escapes URL data. + /// + /// The URL data to escape. + /// An escaped URL data string. + public static string DataEscape(this string url) + => Uri.EscapeDataString(url); - /// - /// Removes multiple consecutive whitespace characters from the specified text. - /// - /// The input text. - /// A string with multiple whitespaces replaced by a single space. - public static string RemoveMultipleWhitespace(this string text) - { - string result = string.Empty; - if (text.IsEmptyOrWhiteSpace()) - { - return result; - } + /// + /// Unescapes URL data. + /// + /// The URL data to unescape. + /// An unescaped URL data string. + public static string DataUnEscape(this string url) + => Uri.UnescapeDataString(url); - var r = new Regex(@"\s+"); - return r.Replace(text, @" "); - } + /// + /// Converts the specified character to lowercase. + /// + /// The character to convert. + /// If set to true, uses culture-invariant conversion; otherwise, uses current culture. + /// The lowercase equivalent of the character. + public static char ToLower(this char c, bool invariant = true) + => invariant ? char.ToLowerInvariant(c) : char.ToLower(c); - /// - /// Escapes a URL string. - /// - /// The URL to escape. - /// An escaped URL string. - [Obsolete] - public static string UrlEscape(this string url) - => Uri.EscapeUriString(url); - - /// - /// Escapes URL data. - /// - /// The URL data to escape. - /// An escaped URL data string. - public static string DataEscape(this string url) - => Uri.EscapeDataString(url); - - /// - /// Unescapes URL data. - /// - /// The URL data to unescape. - /// An unescaped URL data string. - public static string DataUnEscape(this string url) - => Uri.UnescapeDataString(url); - - /// - /// Converts the specified character to lowercase. - /// - /// The character to convert. - /// If set to true, uses culture-invariant conversion; otherwise, uses current culture. - /// The lowercase equivalent of the character. - public static char ToLower(this char c, bool invariant = true) - => invariant ? char.ToLowerInvariant(c) : char.ToLower(c); - - /// - /// Converts the specified character to uppercase. - /// - /// The character to convert. - /// If set to true, uses culture-invariant conversion; otherwise, uses current culture. - /// The uppercase equivalent of the character. - public static char ToUpper(this char c, bool invariant = true) - => invariant ? char.ToUpperInvariant(c) : char.ToUpper(c); - - /// - /// Gets the two-letter ISO language code from the provided culture name. - /// - /// The culture name. - /// The two-letter ISO language code. - public static string GetLangCode(this string cultureName) - => cultureName.To().TwoLetterISOLanguageName; - - /// - /// Removes the specified number of characters from the end of the StringBuilder. - /// - /// The StringBuilder to modify. - /// The number of characters to remove. - public static void RemoveLast(this StringBuilder builder, int count) - => builder.Remove(builder.Length - count, count); - - /// - /// Determines whether the StringBuilder is empty. - /// - /// The StringBuilder to evaluate. - /// True if the StringBuilder is empty; otherwise, false. - public static bool IsEmpty(this StringBuilder builder) - => builder.CheckOnNull(nameof(builder)).Length == 0; - - /// - /// Retrieves the content of the StringBuilder and then clears it. - /// - /// The StringBuilder. - /// The content of the StringBuilder before it was cleared. - public static string GetAndClear(this StringBuilder builder) - { - if (builder is null) - throw new ArgumentNullException(nameof(builder)); + /// + /// Converts the specified character to uppercase. + /// + /// The character to convert. + /// If set to true, uses culture-invariant conversion; otherwise, uses current culture. + /// The uppercase equivalent of the character. + public static char ToUpper(this char c, bool invariant = true) + => invariant ? char.ToUpperInvariant(c) : char.ToUpper(c); - var str = builder.ToString(); - builder.Clear(); - return str; - } + /// + /// Gets the two-letter ISO language code from the provided culture name. + /// + /// The culture name. + /// The two-letter ISO language code. + public static string GetLangCode(this string cultureName) + => cultureName.To().TwoLetterISOLanguageName; + + /// + /// Removes the specified number of characters from the end of the StringBuilder. + /// + /// The StringBuilder to modify. + /// The number of characters to remove. + public static void RemoveLast(this StringBuilder builder, int count) + => builder.Remove(builder.Length - count, count); + + /// + /// Determines whether the StringBuilder is empty. + /// + /// The StringBuilder to evaluate. + /// True if the StringBuilder is empty; otherwise, false. + public static bool IsEmpty(this StringBuilder builder) + => builder.CheckOnNull(nameof(builder)).Length == 0; + + /// + /// Retrieves the content of the StringBuilder and then clears it. + /// + /// The StringBuilder. + /// The content of the StringBuilder before it was cleared. + public static string GetAndClear(this StringBuilder builder) + { + if (builder is null) + throw new ArgumentNullException(nameof(builder)); - /// - /// Interns the specified string. - /// - /// The string to intern. - /// The interned string. - public static string Intern(this string str) - => string.Intern(str); + var str = builder.ToString(); + builder.Clear(); + return str; } + + /// + /// Interns the specified string. + /// + /// The string to intern. + /// The interned string. + public static string Intern(this string str) + => string.Intern(str); } \ No newline at end of file diff --git a/Common/SyncObject.cs b/Common/SyncObject.cs index efef125b..7ff1411a 100644 --- a/Common/SyncObject.cs +++ b/Common/SyncObject.cs @@ -1,146 +1,145 @@ -namespace Ecng.Common +namespace Ecng.Common; + +using System; +using System.Threading; + +/// +/// Provides a synchronization object that encapsulates thread synchronization mechanisms using Monitor. +/// +public class SyncObject { - using System; - using System.Threading; + private bool _processed; + private object _state; /// - /// Provides a synchronization object that encapsulates thread synchronization mechanisms using Monitor. + /// Attempts to enter the synchronization block, with an optional timeout. /// - public class SyncObject + /// The timeout duration. If null, the method does not specify a timeout. + /// true if the synchronization lock was successfully acquired; otherwise, false. + public bool TryEnter(TimeSpan? timeOut = null) { - private bool _processed; - private object _state; - - /// - /// Attempts to enter the synchronization block, with an optional timeout. - /// - /// The timeout duration. If null, the method does not specify a timeout. - /// true if the synchronization lock was successfully acquired; otherwise, false. - public bool TryEnter(TimeSpan? timeOut = null) - { - return timeOut is null ? Monitor.TryEnter(this) : Monitor.TryEnter(this, timeOut.Value); - } + return timeOut is null ? Monitor.TryEnter(this) : Monitor.TryEnter(this, timeOut.Value); + } - /// - /// Enters the synchronization block, waiting indefinitely until the lock is acquired. - /// - public void Enter() - { - Monitor.Enter(this); - } + /// + /// Enters the synchronization block, waiting indefinitely until the lock is acquired. + /// + public void Enter() + { + Monitor.Enter(this); + } - /// - /// Exits the synchronization block. - /// - public void Exit() - { - Monitor.Exit(this); - } + /// + /// Exits the synchronization block. + /// + public void Exit() + { + Monitor.Exit(this); + } - /// - /// Sends a pulse to a single waiting thread. - /// - public void Pulse() - { - Pulse(null); - } + /// + /// Sends a pulse to a single waiting thread. + /// + public void Pulse() + { + Pulse(null); + } - /// - /// Sends a pulse to a single waiting thread and sets the optional state. - /// - /// An optional state object to pass along with the pulse. - public void Pulse(object state) + /// + /// Sends a pulse to a single waiting thread and sets the optional state. + /// + /// An optional state object to pass along with the pulse. + public void Pulse(object state) + { + lock (this) { - lock (this) - { - _state = state; - Monitor.Pulse(this); - } + _state = state; + Monitor.Pulse(this); } + } - /// - /// Sends a pulse to all waiting threads. - /// - public void PulseAll() - { - PulseAll(null); - } + /// + /// Sends a pulse to all waiting threads. + /// + public void PulseAll() + { + PulseAll(null); + } - /// - /// Sends a pulse to all waiting threads and sets the optional state. - /// - /// An optional state object to pass along with the pulse. - public void PulseAll(object state) + /// + /// Sends a pulse to all waiting threads and sets the optional state. + /// + /// An optional state object to pass along with the pulse. + public void PulseAll(object state) + { + lock (this) { - lock (this) - { - _state = state; - Monitor.PulseAll(this); - } + _state = state; + Monitor.PulseAll(this); } + } - /// - /// Signals a waiting thread by sending a pulse along with setting the processed flag. - /// - /// An optional state object to pass along with the pulse. - public void PulseSignal(object state = null) + /// + /// Signals a waiting thread by sending a pulse along with setting the processed flag. + /// + /// An optional state object to pass along with the pulse. + public void PulseSignal(object state = null) + { + lock (this) { - lock (this) - { - _processed = true; - _state = state; - Monitor.Pulse(this); - } + _processed = true; + _state = state; + Monitor.Pulse(this); } + } - /// - /// Waits for a pulse with an optional timeout. - /// - /// The timeout duration. If null, waits indefinitely. - /// true if the pulse was received; otherwise, false (in case of timeout). - public bool Wait(TimeSpan? timeOut = null) - { - lock (this) - return WaitInternal(timeOut); - } + /// + /// Waits for a pulse with an optional timeout. + /// + /// The timeout duration. If null, waits indefinitely. + /// true if the pulse was received; otherwise, false (in case of timeout). + public bool Wait(TimeSpan? timeOut = null) + { + lock (this) + return WaitInternal(timeOut); + } - /// - /// Waits for a signal with an optional timeout. - /// - /// The timeout duration. If null, waits indefinitely. - /// true if the signal was received; otherwise, false (in case of timeout). - public bool WaitSignal(TimeSpan? timeOut = null) - { - return WaitSignal(timeOut, out _); - } + /// + /// Waits for a signal with an optional timeout. + /// + /// The timeout duration. If null, waits indefinitely. + /// true if the signal was received; otherwise, false (in case of timeout). + public bool WaitSignal(TimeSpan? timeOut = null) + { + return WaitSignal(timeOut, out _); + } - /// - /// Waits for a signal with an optional timeout and outputs the state associated with the signal. - /// - /// The timeout duration. If null, waits indefinitely. - /// The state object passed by the signaling call. - /// true if a signal was received; otherwise, false (in case of timeout). - public bool WaitSignal(TimeSpan? timeOut, out object state) + /// + /// Waits for a signal with an optional timeout and outputs the state associated with the signal. + /// + /// The timeout duration. If null, waits indefinitely. + /// The state object passed by the signaling call. + /// true if a signal was received; otherwise, false (in case of timeout). + public bool WaitSignal(TimeSpan? timeOut, out object state) + { + lock (this) { - lock (this) - { - var result = _processed || WaitInternal(timeOut); - _processed = false; - state = _state; - return result; - } + var result = _processed || WaitInternal(timeOut); + _processed = false; + state = _state; + return result; } + } - /// - /// Waits internally for a pulse with an optional timeout. - /// - /// The timeout duration. If null, waits indefinitely. - /// true if the pulse was received; otherwise, false (in case of timeout). - private bool WaitInternal(TimeSpan? timeOut) - { - return timeOut is null - ? Monitor.Wait(this) - : Monitor.Wait(this, timeOut.Value); - } + /// + /// Waits internally for a pulse with an optional timeout. + /// + /// The timeout duration. If null, waits indefinitely. + /// true if the pulse was received; otherwise, false (in case of timeout). + private bool WaitInternal(TimeSpan? timeOut) + { + return timeOut is null + ? Monitor.Wait(this) + : Monitor.Wait(this, timeOut.Value); } } diff --git a/Common/TargetPlatformAttribute.cs b/Common/TargetPlatformAttribute.cs index e94d3c68..807b16a3 100644 --- a/Common/TargetPlatformAttribute.cs +++ b/Common/TargetPlatformAttribute.cs @@ -1,20 +1,19 @@ -namespace Ecng.Common -{ - using System; +namespace Ecng.Common; + +using System; +/// +/// Features attribute. +/// +/// +/// Initializes a new instance of the . +/// +/// Platform. +[AttributeUsage(AttributeTargets.Class)] +public class TargetPlatformAttribute(Platforms platform = Platforms.AnyCPU) : Attribute +{ /// - /// Features attribute. + /// Platform. /// - /// - /// Initializes a new instance of the . - /// - /// Platform. - [AttributeUsage(AttributeTargets.Class)] - public class TargetPlatformAttribute(Platforms platform = Platforms.AnyCPU) : Attribute - { - /// - /// Platform. - /// - public Platforms Platform { get; } = platform; - } + public Platforms Platform { get; } = platform; } \ No newline at end of file diff --git a/Common/ThreadingHelper.cs b/Common/ThreadingHelper.cs index be563e14..cc42e0e4 100644 --- a/Common/ThreadingHelper.cs +++ b/Common/ThreadingHelper.cs @@ -1,558 +1,557 @@ -namespace Ecng.Common -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.Threading; +namespace Ecng.Common; + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Threading; +/// +/// Provides helper methods for thread and timer operations. +/// +public static class ThreadingHelper +{ /// - /// Provides helper methods for thread and timer operations. + /// Creates a Timer that executes the specified action with invariant culture. /// - public static class ThreadingHelper + /// The action to be executed by the timer. + /// A new Timer instance. + public static Timer TimerInvariant(this Action handler) { - /// - /// Creates a Timer that executes the specified action with invariant culture. - /// - /// The action to be executed by the timer. - /// A new Timer instance. - public static Timer TimerInvariant(this Action handler) - { - return handler.AsInvariant().Timer(); - } + return handler.AsInvariant().Timer(); + } - /// - /// Creates a Timer that executes the specified action. - /// - /// The action to be executed by the timer. - /// A new Timer instance. - public static Timer Timer(this Action handler) - { - if (handler is null) - throw new ArgumentNullException(nameof(handler)); + /// + /// Creates a Timer that executes the specified action. + /// + /// The action to be executed by the timer. + /// A new Timer instance. + public static Timer Timer(this Action handler) + { + if (handler is null) + throw new ArgumentNullException(nameof(handler)); - return CreateTimer(s => handler()); - } + return CreateTimer(s => handler()); + } - /// - /// Creates a Timer that executes the specified action with one argument. - /// - /// The type of the argument. - /// The action to be executed by the timer. - /// The argument passed to the action. - /// A new Timer instance. - public static Timer Timer(this Action handler, T arg) - { - if (handler is null) - throw new ArgumentNullException(nameof(handler)); + /// + /// Creates a Timer that executes the specified action with one argument. + /// + /// The type of the argument. + /// The action to be executed by the timer. + /// The argument passed to the action. + /// A new Timer instance. + public static Timer Timer(this Action handler, T arg) + { + if (handler is null) + throw new ArgumentNullException(nameof(handler)); - return CreateTimer(s => handler(arg)); - } + return CreateTimer(s => handler(arg)); + } - /// - /// Creates a Timer that executes the specified action with two arguments. - /// - /// The type of the first argument. - /// The type of the second argument. - /// The action to be executed by the timer. - /// The first argument passed to the action. - /// The second argument passed to the action. - /// A new Timer instance. - public static Timer Timer(this Action handler, T1 arg1, T2 arg2) - { - if (handler is null) - throw new ArgumentNullException(nameof(handler)); + /// + /// Creates a Timer that executes the specified action with two arguments. + /// + /// The type of the first argument. + /// The type of the second argument. + /// The action to be executed by the timer. + /// The first argument passed to the action. + /// The second argument passed to the action. + /// A new Timer instance. + public static Timer Timer(this Action handler, T1 arg1, T2 arg2) + { + if (handler is null) + throw new ArgumentNullException(nameof(handler)); - return CreateTimer(s => handler(arg1, arg2)); - } + return CreateTimer(s => handler(arg1, arg2)); + } - /// - /// Creates a Timer that executes the specified action with three arguments. - /// - /// The type of the first argument. - /// The type of the second argument. - /// The type of the third argument. - /// The action to be executed by the timer. - /// The first argument passed to the action. - /// The second argument passed to the action. - /// The third argument passed to the action. - /// A new Timer instance. - public static Timer Timer(this Action handler, T1 arg1, T2 arg2, T3 arg3) - { - if (handler is null) - throw new ArgumentNullException(nameof(handler)); + /// + /// Creates a Timer that executes the specified action with three arguments. + /// + /// The type of the first argument. + /// The type of the second argument. + /// The type of the third argument. + /// The action to be executed by the timer. + /// The first argument passed to the action. + /// The second argument passed to the action. + /// The third argument passed to the action. + /// A new Timer instance. + public static Timer Timer(this Action handler, T1 arg1, T2 arg2, T3 arg3) + { + if (handler is null) + throw new ArgumentNullException(nameof(handler)); - return CreateTimer(s => handler(arg1, arg2, arg3)); - } + return CreateTimer(s => handler(arg1, arg2, arg3)); + } - /// - /// Creates a Timer that executes the specified action with four arguments. - /// - /// The type of the first argument. - /// The type of the second argument. - /// The type of the third argument. - /// The type of the fourth argument. - /// The action to be executed by the timer. - /// The first argument passed to the action. - /// The second argument passed to the action. - /// The third argument passed to the action. - /// The fourth argument passed to the action. - /// A new Timer instance. - public static Timer Timer(this Action handler, T1 arg1, T2 arg2, T3 arg3, T4 arg4) - { - if (handler is null) - throw new ArgumentNullException(nameof(handler)); + /// + /// Creates a Timer that executes the specified action with four arguments. + /// + /// The type of the first argument. + /// The type of the second argument. + /// The type of the third argument. + /// The type of the fourth argument. + /// The action to be executed by the timer. + /// The first argument passed to the action. + /// The second argument passed to the action. + /// The third argument passed to the action. + /// The fourth argument passed to the action. + /// A new Timer instance. + public static Timer Timer(this Action handler, T1 arg1, T2 arg2, T3 arg3, T4 arg4) + { + if (handler is null) + throw new ArgumentNullException(nameof(handler)); - return CreateTimer(s => handler(arg1, arg2, arg3, arg4)); - } + return CreateTimer(s => handler(arg1, arg2, arg3, arg4)); + } - private static readonly Dictionary _intervals = []; + private static readonly Dictionary _intervals = []; - /// - /// Gets the interval associated with the specified timer. - /// - /// The timer whose interval is retrieved. - /// The TimeSpan interval of the timer. - public static TimeSpan Interval(this Timer timer) + /// + /// Gets the interval associated with the specified timer. + /// + /// The timer whose interval is retrieved. + /// The TimeSpan interval of the timer. + public static TimeSpan Interval(this Timer timer) + { + lock (_intervals) { - lock (_intervals) - { - _intervals.TryGetValue(timer, out var interval); - return interval; - } + _intervals.TryGetValue(timer, out var interval); + return interval; } + } - /// - /// Sets the start and execution interval for the specified timer. - /// - /// The timer to configure. - /// The interval between timer executions. - /// The configured timer. - public static Timer Interval(this Timer timer, TimeSpan interval) - { - return timer.Interval(interval, interval); - } + /// + /// Sets the start and execution interval for the specified timer. + /// + /// The timer to configure. + /// The interval between timer executions. + /// The configured timer. + public static Timer Interval(this Timer timer, TimeSpan interval) + { + return timer.Interval(interval, interval); + } - /// - /// Sets the start delay and execution interval for the specified timer. - /// - /// The timer to configure. - /// The start delay before the timer begins execution. - /// The interval between timer executions. - /// The configured timer. - public static Timer Interval(this Timer timer, TimeSpan start, TimeSpan interval) - { - if (timer is null) - throw new ArgumentNullException(nameof(timer)); + /// + /// Sets the start delay and execution interval for the specified timer. + /// + /// The timer to configure. + /// The start delay before the timer begins execution. + /// The interval between timer executions. + /// The configured timer. + public static Timer Interval(this Timer timer, TimeSpan start, TimeSpan interval) + { + if (timer is null) + throw new ArgumentNullException(nameof(timer)); - timer.Change(start, interval); + timer.Change(start, interval); - lock(_intervals) - _intervals[timer] = interval; + lock(_intervals) + _intervals[timer] = interval; - return timer; - } + return timer; + } - /// - /// Creates a Timer using the specified callback. - /// - /// The TimerCallback to be executed. - /// A new Timer instance. - private static Timer CreateTimer(TimerCallback callback) - { - return new Timer(callback); - } + /// + /// Creates a Timer using the specified callback. + /// + /// The TimerCallback to be executed. + /// A new Timer instance. + private static Timer CreateTimer(TimerCallback callback) + { + return new Timer(callback); + } - /// - /// Creates a Thread that executes the specified action with invariant culture. - /// - /// The action to be executed by the thread. - /// A new Thread instance. - public static Thread ThreadInvariant(this Action handler) - { - return handler.AsInvariant().Thread(); - } + /// + /// Creates a Thread that executes the specified action with invariant culture. + /// + /// The action to be executed by the thread. + /// A new Thread instance. + public static Thread ThreadInvariant(this Action handler) + { + return handler.AsInvariant().Thread(); + } - /// - /// Creates a Thread that executes the specified action. - /// - /// The action to be executed by the thread. - /// A new Thread instance. - public static Thread Thread(this Action handler) - { - if (handler is null) - throw new ArgumentNullException(nameof(handler)); + /// + /// Creates a Thread that executes the specified action. + /// + /// The action to be executed by the thread. + /// A new Thread instance. + public static Thread Thread(this Action handler) + { + if (handler is null) + throw new ArgumentNullException(nameof(handler)); - return CreateThread(() => handler()); - } + return CreateThread(() => handler()); + } - /// - /// Creates a Thread that executes the specified action with one argument. - /// - /// The type of the argument. - /// The action to be executed by the thread. - /// The argument passed to the action. - /// A new Thread instance. - public static Thread Thread(this Action handler, T arg) - { - if (handler is null) - throw new ArgumentNullException(nameof(handler)); + /// + /// Creates a Thread that executes the specified action with one argument. + /// + /// The type of the argument. + /// The action to be executed by the thread. + /// The argument passed to the action. + /// A new Thread instance. + public static Thread Thread(this Action handler, T arg) + { + if (handler is null) + throw new ArgumentNullException(nameof(handler)); - return CreateThread(() => handler(arg)); - } + return CreateThread(() => handler(arg)); + } - /// - /// Creates a Thread that executes the specified action with two arguments. - /// - /// The type of the first argument. - /// The type of the second argument. - /// The action to be executed by the thread. - /// The first argument passed to the action. - /// The second argument passed to the action. - /// A new Thread instance. - public static Thread Thread(this Action handler, T1 arg1, T2 arg2) - { - if (handler is null) - throw new ArgumentNullException(nameof(handler)); + /// + /// Creates a Thread that executes the specified action with two arguments. + /// + /// The type of the first argument. + /// The type of the second argument. + /// The action to be executed by the thread. + /// The first argument passed to the action. + /// The second argument passed to the action. + /// A new Thread instance. + public static Thread Thread(this Action handler, T1 arg1, T2 arg2) + { + if (handler is null) + throw new ArgumentNullException(nameof(handler)); - return CreateThread(() => handler(arg1, arg2)); - } + return CreateThread(() => handler(arg1, arg2)); + } - /// - /// Creates a Thread that executes the specified action with three arguments. - /// - /// The type of the first argument. - /// The type of the second argument. - /// The type of the third argument. - /// The action to be executed by the thread. - /// The first argument passed to the action. - /// The second argument passed to the action. - /// The third argument passed to the action. - /// A new Thread instance. - public static Thread Thread(this Action handler, T1 arg1, T2 arg2, T3 arg3) - { - if (handler is null) - throw new ArgumentNullException(nameof(handler)); + /// + /// Creates a Thread that executes the specified action with three arguments. + /// + /// The type of the first argument. + /// The type of the second argument. + /// The type of the third argument. + /// The action to be executed by the thread. + /// The first argument passed to the action. + /// The second argument passed to the action. + /// The third argument passed to the action. + /// A new Thread instance. + public static Thread Thread(this Action handler, T1 arg1, T2 arg2, T3 arg3) + { + if (handler is null) + throw new ArgumentNullException(nameof(handler)); - return CreateThread(() => handler(arg1, arg2, arg3)); - } + return CreateThread(() => handler(arg1, arg2, arg3)); + } - /// - /// Creates a Thread that executes the specified action with four arguments. - /// - /// The type of the first argument. - /// The type of the second argument. - /// The type of the third argument. - /// The type of the fourth argument. - /// The action to be executed by the thread. - /// The first argument passed to the action. - /// The second argument passed to the action. - /// The third argument passed to the action. - /// The fourth argument passed to the action. - /// A new Thread instance. - public static Thread Thread(this Action handler, T1 arg1, T2 arg2, T3 arg3, T4 arg4) - { - if (handler is null) - throw new ArgumentNullException(nameof(handler)); + /// + /// Creates a Thread that executes the specified action with four arguments. + /// + /// The type of the first argument. + /// The type of the second argument. + /// The type of the third argument. + /// The type of the fourth argument. + /// The action to be executed by the thread. + /// The first argument passed to the action. + /// The second argument passed to the action. + /// The third argument passed to the action. + /// The fourth argument passed to the action. + /// A new Thread instance. + public static Thread Thread(this Action handler, T1 arg1, T2 arg2, T3 arg3, T4 arg4) + { + if (handler is null) + throw new ArgumentNullException(nameof(handler)); - return CreateThread(() => handler(arg1, arg2, arg3, arg4)); - } + return CreateThread(() => handler(arg1, arg2, arg3, arg4)); + } - /// - /// Creates a new background thread using the specified start method. - /// - /// The ThreadStart delegate that represents the method to be invoked when the thread begins executing. - /// A new Thread instance set as a background thread. - private static Thread CreateThread(ThreadStart start) - { - return new Thread(start) { IsBackground = true }; - } + /// + /// Creates a new background thread using the specified start method. + /// + /// The ThreadStart delegate that represents the method to be invoked when the thread begins executing. + /// A new Thread instance set as a background thread. + private static Thread CreateThread(ThreadStart start) + { + return new Thread(start) { IsBackground = true }; + } - /// - /// Sets the name of the specified thread. - /// - /// The thread to name. - /// The name to set. - /// The thread with the assigned name. - public static Thread Name(this Thread thread, string name) - { - if (thread is null) - throw new ArgumentNullException(nameof(thread)); + /// + /// Sets the name of the specified thread. + /// + /// The thread to name. + /// The name to set. + /// The thread with the assigned name. + public static Thread Name(this Thread thread, string name) + { + if (thread is null) + throw new ArgumentNullException(nameof(thread)); - thread.Name = name; - return thread; - } + thread.Name = name; + return thread; + } - /// - /// Sets whether the specified thread is a background thread. - /// - /// The thread to configure. - /// True to set the thread as a background thread; otherwise, false. - /// The configured thread. - public static Thread Background(this Thread thread, bool isBackground) - { - if (thread is null) - throw new ArgumentNullException(nameof(thread)); + /// + /// Sets whether the specified thread is a background thread. + /// + /// The thread to configure. + /// True to set the thread as a background thread; otherwise, false. + /// The configured thread. + public static Thread Background(this Thread thread, bool isBackground) + { + if (thread is null) + throw new ArgumentNullException(nameof(thread)); - thread.IsBackground = isBackground; - return thread; - } + thread.IsBackground = isBackground; + return thread; + } - /// - /// Starts the specified thread. - /// - /// The thread to start. - /// The started thread. - public static Thread Launch(this Thread thread) - { - if (thread is null) - throw new ArgumentNullException(nameof(thread)); + /// + /// Starts the specified thread. + /// + /// The thread to start. + /// The started thread. + public static Thread Launch(this Thread thread) + { + if (thread is null) + throw new ArgumentNullException(nameof(thread)); - thread.Start(); - return thread; - } + thread.Start(); + return thread; + } - /// - /// Suspends the current thread for the specified time interval. - /// - /// The TimeSpan for which to sleep. - public static void Sleep(this TimeSpan timeOut) - { - if (timeOut != TimeSpan.Zero) - System.Threading.Thread.Sleep(timeOut); - } + /// + /// Suspends the current thread for the specified time interval. + /// + /// The TimeSpan for which to sleep. + public static void Sleep(this TimeSpan timeOut) + { + if (timeOut != TimeSpan.Zero) + System.Threading.Thread.Sleep(timeOut); + } - /// - /// Sets the priority of the specified thread. - /// - /// The thread whose priority is to be set. - /// The thread priority to set. - /// The thread with the updated priority. - public static Thread Priority(this Thread thread, ThreadPriority priority) - { - if (thread is null) - throw new ArgumentNullException(nameof(thread)); + /// + /// Sets the priority of the specified thread. + /// + /// The thread whose priority is to be set. + /// The thread priority to set. + /// The thread with the updated priority. + public static Thread Priority(this Thread thread, ThreadPriority priority) + { + if (thread is null) + throw new ArgumentNullException(nameof(thread)); - thread.Priority = priority; - return thread; - } + thread.Priority = priority; + return thread; + } - /// - /// Executes the specified action within a write lock. - /// - /// The ReaderWriterLockSlim instance. - /// The action to execute within the lock. - public static void Write(this ReaderWriterLockSlim rw, Action handler) - { - rw.TryWrite(handler, -1); - } + /// + /// Executes the specified action within a write lock. + /// + /// The ReaderWriterLockSlim instance. + /// The action to execute within the lock. + public static void Write(this ReaderWriterLockSlim rw, Action handler) + { + rw.TryWrite(handler, -1); + } - /// - /// Attempts to execute the specified action within a write lock. - /// - /// The ReaderWriterLockSlim instance. - /// The action to execute within the lock. - /// The timeout in milliseconds to wait for acquiring the lock. - /// True if the lock was acquired and the action executed; otherwise, false. - public static bool TryWrite(this ReaderWriterLockSlim rw, Action handler, int timeOut = 0) - { - if (rw is null) - throw new ArgumentNullException(nameof(rw)); + /// + /// Attempts to execute the specified action within a write lock. + /// + /// The ReaderWriterLockSlim instance. + /// The action to execute within the lock. + /// The timeout in milliseconds to wait for acquiring the lock. + /// True if the lock was acquired and the action executed; otherwise, false. + public static bool TryWrite(this ReaderWriterLockSlim rw, Action handler, int timeOut = 0) + { + if (rw is null) + throw new ArgumentNullException(nameof(rw)); - return Try(rw.TryEnterWriteLock, rw.ExitWriteLock, handler, timeOut); - } + return Try(rw.TryEnterWriteLock, rw.ExitWriteLock, handler, timeOut); + } - /// - /// Executes the specified action within a read lock. - /// - /// The ReaderWriterLockSlim instance. - /// The action to execute within the read lock. - public static void Read(this ReaderWriterLockSlim rw, Action handler) + /// + /// Executes the specified action within a read lock. + /// + /// The ReaderWriterLockSlim instance. + /// The action to execute within the read lock. + public static void Read(this ReaderWriterLockSlim rw, Action handler) + { + rw.TryRead(handler, -1); + } + + /// + /// Attempts to execute the specified action within a read lock. + /// + /// The ReaderWriterLockSlim instance. + /// The action to execute within the read lock. + /// The timeout in milliseconds to wait for acquiring the lock. + /// True if the lock was acquired and the action executed; otherwise, false. + public static bool TryRead(this ReaderWriterLockSlim rw, Action handler, int timeOut = 0) + { + if (rw is null) + throw new ArgumentNullException(nameof(rw)); + + return Try(rw.TryEnterReadLock, rw.ExitReadLock, handler, timeOut); + } + + /// + /// Executes the specified action within an upgradeable read lock. + /// + /// The ReaderWriterLockSlim instance. + /// The action to execute within the upgradeable read lock. + public static void Upgrade(this ReaderWriterLockSlim rw, Action handler) + { + rw.TryUpgrade(handler, -1); + } + + /// + /// Attempts to execute the specified action within an upgradeable read lock. + /// + /// The ReaderWriterLockSlim instance. + /// The action to execute within the upgradeable read lock. + /// The timeout in milliseconds to wait for acquiring the lock. + /// True if the lock was acquired and the action executed; otherwise, false. + public static bool TryUpgrade(this ReaderWriterLockSlim rw, Action handler, int timeOut = 0) + { + if (rw is null) + throw new ArgumentNullException(nameof(rw)); + + return Try(rw.TryEnterUpgradeableReadLock, rw.ExitUpgradeableReadLock, handler, timeOut); + } + + /// + /// Helper method to attempt entering a lock, executing an action, and releasing the lock. + /// + /// Function to attempt entering the lock. + /// Action to exit the lock. + /// The action to execute once the lock is acquired. + /// The timeout in milliseconds to wait for the lock. + /// True if the action is executed; otherwise, false. + private static bool Try(Func enter, Action exit, Action handler, int timeOut = 0) + { + if (handler is null) + throw new ArgumentNullException(nameof(handler)); + + if (!enter(timeOut)) + return false; + + try + { + handler(); + } + finally { - rw.TryRead(handler, -1); + exit(); } + return true; + } + + /// + /// Provides a disposable wrapper for ReaderWriterLockSlim that enters a lock on creation and exits on disposal. + /// + private struct ReaderWriterLockSlimDispose : IDisposable + { + private readonly ReaderWriterLockSlim _rwLock; + private readonly bool _isRead; + /// - /// Attempts to execute the specified action within a read lock. + /// Initializes a new instance of the ReaderWriterLockSlimDispose struct. /// - /// The ReaderWriterLockSlim instance. - /// The action to execute within the read lock. - /// The timeout in milliseconds to wait for acquiring the lock. - /// True if the lock was acquired and the action executed; otherwise, false. - public static bool TryRead(this ReaderWriterLockSlim rw, Action handler, int timeOut = 0) + /// The ReaderWriterLockSlim instance. + /// True to enter a read lock; false to enter a write lock. + public ReaderWriterLockSlimDispose(ReaderWriterLockSlim rwLock, bool isRead) { - if (rw is null) - throw new ArgumentNullException(nameof(rw)); + _rwLock = rwLock ?? throw new ArgumentNullException(nameof(rwLock)); + _isRead = isRead; - return Try(rw.TryEnterReadLock, rw.ExitReadLock, handler, timeOut); + if (_isRead) + rwLock.EnterReadLock(); + else + rwLock.EnterWriteLock(); } /// - /// Executes the specified action within an upgradeable read lock. + /// Exits the lock when disposed. /// - /// The ReaderWriterLockSlim instance. - /// The action to execute within the upgradeable read lock. - public static void Upgrade(this ReaderWriterLockSlim rw, Action handler) + void IDisposable.Dispose() { - rw.TryUpgrade(handler, -1); + if (_isRead) + _rwLock.ExitReadLock(); + else + _rwLock.ExitWriteLock(); } + } - /// - /// Attempts to execute the specified action within an upgradeable read lock. - /// - /// The ReaderWriterLockSlim instance. - /// The action to execute within the upgradeable read lock. - /// The timeout in milliseconds to wait for acquiring the lock. - /// True if the lock was acquired and the action executed; otherwise, false. - public static bool TryUpgrade(this ReaderWriterLockSlim rw, Action handler, int timeOut = 0) - { - if (rw is null) - throw new ArgumentNullException(nameof(rw)); + /// + /// Acquires a write lock and returns an IDisposable that releases the lock when disposed. + /// + /// The ReaderWriterLockSlim instance. + /// An IDisposable that releases the write lock. + public static IDisposable WriterLock(this ReaderWriterLockSlim rwLock) + => new ReaderWriterLockSlimDispose(rwLock, false); - return Try(rw.TryEnterUpgradeableReadLock, rw.ExitUpgradeableReadLock, handler, timeOut); - } + /// + /// Acquires a read lock and returns an IDisposable that releases the lock when disposed. + /// + /// The ReaderWriterLockSlim instance. + /// An IDisposable that releases the read lock. + public static IDisposable ReaderLock(this ReaderWriterLockSlim rwLock) + => new ReaderWriterLockSlimDispose(rwLock, true); - /// - /// Helper method to attempt entering a lock, executing an action, and releasing the lock. - /// - /// Function to attempt entering the lock. - /// Action to exit the lock. - /// The action to execute once the lock is acquired. - /// The timeout in milliseconds to wait for the lock. - /// True if the action is executed; otherwise, false. - private static bool Try(Func enter, Action exit, Action handler, int timeOut = 0) - { - if (handler is null) - throw new ArgumentNullException(nameof(handler)); + /// + /// Tries to get a unique Mutex with the specified name. + /// + /// The name of the Mutex. + /// When this method returns, contains the Mutex if successful. + /// True if the Mutex is unique and acquired; otherwise, false. + public static bool TryGetUniqueMutex(string name, out Mutex mutex) + { + mutex = new Mutex(false, name); - if (!enter(timeOut)) + try + { + if (!mutex.WaitOne(1)) return false; - try - { - handler(); - } - finally - { - exit(); - } - - return true; + mutex = new Mutex(true, name); } - - /// - /// Provides a disposable wrapper for ReaderWriterLockSlim that enters a lock on creation and exits on disposal. - /// - private struct ReaderWriterLockSlimDispose : IDisposable + catch (AbandonedMutexException) { - private readonly ReaderWriterLockSlim _rwLock; - private readonly bool _isRead; - - /// - /// Initializes a new instance of the ReaderWriterLockSlimDispose struct. - /// - /// The ReaderWriterLockSlim instance. - /// True to enter a read lock; false to enter a write lock. - public ReaderWriterLockSlimDispose(ReaderWriterLockSlim rwLock, bool isRead) - { - _rwLock = rwLock ?? throw new ArgumentNullException(nameof(rwLock)); - _isRead = isRead; - - if (_isRead) - rwLock.EnterReadLock(); - else - rwLock.EnterWriteLock(); - } - - /// - /// Exits the lock when disposed. - /// - void IDisposable.Dispose() - { - if (_isRead) - _rwLock.ExitReadLock(); - else - _rwLock.ExitWriteLock(); - } + // http://stackoverflow.com/questions/15456986/how-to-gracefully-get-out-of-abandonedmutexexception + // The previous process did not release the mutex. + // When catching the exception, the current process becomes the owner. } - /// - /// Acquires a write lock and returns an IDisposable that releases the lock when disposed. - /// - /// The ReaderWriterLockSlim instance. - /// An IDisposable that releases the write lock. - public static IDisposable WriterLock(this ReaderWriterLockSlim rwLock) - => new ReaderWriterLockSlimDispose(rwLock, false); + return true; + } - /// - /// Acquires a read lock and returns an IDisposable that releases the lock when disposed. - /// - /// The ReaderWriterLockSlim instance. - /// An IDisposable that releases the read lock. - public static IDisposable ReaderLock(this ReaderWriterLockSlim rwLock) - => new ReaderWriterLockSlimDispose(rwLock, true); + private class CultureHolder : Disposable + { + private readonly CultureInfo _culture; /// - /// Tries to get a unique Mutex with the specified name. + /// Initializes a new instance of the CultureHolder class, capturing the current culture. /// - /// The name of the Mutex. - /// When this method returns, contains the Mutex if successful. - /// True if the Mutex is unique and acquired; otherwise, false. - public static bool TryGetUniqueMutex(string name, out Mutex mutex) - { - mutex = new Mutex(false, name); - - try - { - if (!mutex.WaitOne(1)) - return false; - - mutex = new Mutex(true, name); - } - catch (AbandonedMutexException) - { - // http://stackoverflow.com/questions/15456986/how-to-gracefully-get-out-of-abandonedmutexexception - // The previous process did not release the mutex. - // When catching the exception, the current process becomes the owner. - } - - return true; - } - - private class CultureHolder : Disposable - { - private readonly CultureInfo _culture; - - /// - /// Initializes a new instance of the CultureHolder class, capturing the current culture. - /// - public CultureHolder() => _culture = System.Threading.Thread.CurrentThread.CurrentCulture; - - /// - /// Restores the original culture when disposed. - /// - protected override void DisposeManaged() - { - System.Threading.Thread.CurrentThread.CurrentCulture = _culture; - base.DisposeManaged(); - } - } + public CultureHolder() => _culture = System.Threading.Thread.CurrentThread.CurrentCulture; /// - /// Temporarily sets the current thread's culture to the specified culture. + /// Restores the original culture when disposed. /// - /// The CultureInfo to be set for the current thread. - /// An IDisposable that, when disposed, restores the original culture. - public static IDisposable WithCulture(CultureInfo culture) + protected override void DisposeManaged() { - var holder = new CultureHolder(); - System.Threading.Thread.CurrentThread.CurrentCulture = culture; - return holder; + System.Threading.Thread.CurrentThread.CurrentCulture = _culture; + base.DisposeManaged(); } + } - /// - /// Temporarily sets the current thread's culture to the invariant culture. - /// - /// An IDisposable that, when disposed, restores the original culture. - public static IDisposable WithInvariantCulture() => WithCulture(CultureInfo.InvariantCulture); + /// + /// Temporarily sets the current thread's culture to the specified culture. + /// + /// The CultureInfo to be set for the current thread. + /// An IDisposable that, when disposed, restores the original culture. + public static IDisposable WithCulture(CultureInfo culture) + { + var holder = new CultureHolder(); + System.Threading.Thread.CurrentThread.CurrentCulture = culture; + return holder; } + + /// + /// Temporarily sets the current thread's culture to the invariant culture. + /// + /// An IDisposable that, when disposed, restores the original culture. + public static IDisposable WithInvariantCulture() => WithCulture(CultureInfo.InvariantCulture); } \ No newline at end of file diff --git a/Common/TimeHelper.cs b/Common/TimeHelper.cs index 1f6fae09..7c2d888a 100644 --- a/Common/TimeHelper.cs +++ b/Common/TimeHelper.cs @@ -1,1075 +1,1074 @@ -namespace Ecng.Common +namespace Ecng.Common; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; + +/// +/// Provides various helper methods and properties for working with dates, times, and time offsets. +/// +public static class TimeHelper { - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; + private static readonly Stopwatch _timer; + private static readonly DateTime _start; + private static DateTime _startWithOffset; + + static TimeHelper() + { + _start = DateTime.Now; + _timer = Stopwatch.StartNew(); + + NowOffset = TimeSpan.Zero; + } /// - /// Provides various helper methods and properties for working with dates, times, and time offsets. + /// Gets the current time including the configured offset. /// - public static class TimeHelper - { - private static readonly Stopwatch _timer; - private static readonly DateTime _start; - private static DateTime _startWithOffset; + public static DateTime Now => _startWithOffset + _timer.Elapsed; - static TimeHelper() - { - _start = DateTime.Now; - _timer = Stopwatch.StartNew(); + /// + /// Gets the current time as a including the local offset. + /// + public static DateTimeOffset NowWithOffset => Now.ApplyLocal(); + + private static TimeSpan _nowOffset; - NowOffset = TimeSpan.Zero; + /// + /// Gets or sets the offset applied to the current time when retrieving . + /// + public static TimeSpan NowOffset + { + get => _nowOffset; + set + { + _nowOffset = value; + _startWithOffset = _start + value; } + } - /// - /// Gets the current time including the configured offset. - /// - public static DateTime Now => _startWithOffset + _timer.Elapsed; + /// + /// Gets or sets the time zone offset used for calculations. + /// + public static TimeSpan TimeZoneOffset { get; set; } = TimeZoneInfo.Local.BaseUtcOffset; - /// - /// Gets the current time as a including the local offset. - /// - public static DateTimeOffset NowWithOffset => Now.ApplyLocal(); + /// + /// Synchronizes the current offset by comparing local time with an NTP server. + /// + /// The synchronization timeout in milliseconds. + public static void SyncMarketTime(int timeout = 5000) + { + var dtNow = _start + _timer.Elapsed; + NowOffset = new NtpClient().GetLocalTime(TimeZoneInfo.Local, timeout).Subtract(dtNow); + } - private static TimeSpan _nowOffset; + /// + /// Returns total weeks in the specified . + /// + public static double TotalWeeks(this TimeSpan value) + { + return (double)value.Ticks / TicksPerWeek; + } - /// - /// Gets or sets the offset applied to the current time when retrieving . - /// - public static TimeSpan NowOffset - { - get => _nowOffset; - set - { - _nowOffset = value; - _startWithOffset = _start + value; - } - } + /// + /// Returns total months in the specified . + /// + public static double TotalMonths(this TimeSpan value) + { + return (double)value.Ticks / TicksPerMonth; + } - /// - /// Gets or sets the time zone offset used for calculations. - /// - public static TimeSpan TimeZoneOffset { get; set; } = TimeZoneInfo.Local.BaseUtcOffset; + /// + /// Returns total years in the specified . + /// + public static double TotalYears(this TimeSpan value) + { + return (double)value.Ticks / TicksPerYear; + } - /// - /// Synchronizes the current offset by comparing local time with an NTP server. - /// - /// The synchronization timeout in milliseconds. - public static void SyncMarketTime(int timeout = 5000) - { - var dtNow = _start + _timer.Elapsed; - NowOffset = new NtpClient().GetLocalTime(TimeZoneInfo.Local, timeout).Subtract(dtNow); - } + /// + /// Returns total centuries in the specified . + /// + public static double TotalCenturies(this TimeSpan value) + { + return (double)value.Ticks / TicksPerCentury; + } - /// - /// Returns total weeks in the specified . - /// - public static double TotalWeeks(this TimeSpan value) - { - return (double)value.Ticks / TicksPerWeek; - } + /// + /// Returns total millenniums in the specified . + /// + public static double TotalMilleniums(this TimeSpan value) + { + return (double)value.Ticks / TicksPerMillenium; + } - /// - /// Returns total months in the specified . - /// - public static double TotalMonths(this TimeSpan value) - { - return (double)value.Ticks / TicksPerMonth; - } + /// + /// Represents the number of ticks in 1 nanosecond. + /// + public const double TicksPerNanosecond = 1.0 / NanosecondsPerTick; - /// - /// Returns total years in the specified . - /// - public static double TotalYears(this TimeSpan value) - { - return (double)value.Ticks / TicksPerYear; - } + /// + /// Represents the number of nanoseconds in 1 tick. + /// + public const long NanosecondsPerTick = 100; - /// - /// Returns total centuries in the specified . - /// - public static double TotalCenturies(this TimeSpan value) - { - return (double)value.Ticks / TicksPerCentury; - } + /// + /// Represents the number of ticks in 1 microsecond. + /// + public const long TicksPerMicrosecond = TimeSpan.TicksPerMillisecond / 1000; - /// - /// Returns total millenniums in the specified . - /// - public static double TotalMilleniums(this TimeSpan value) - { - return (double)value.Ticks / TicksPerMillenium; - } + /// + /// Represents the number of ticks in 1 week. + /// + public const long TicksPerWeek = TimeSpan.TicksPerDay * 7; - /// - /// Represents the number of ticks in 1 nanosecond. - /// - public const double TicksPerNanosecond = 1.0 / NanosecondsPerTick; - - /// - /// Represents the number of nanoseconds in 1 tick. - /// - public const long NanosecondsPerTick = 100; - - /// - /// Represents the number of ticks in 1 microsecond. - /// - public const long TicksPerMicrosecond = TimeSpan.TicksPerMillisecond / 1000; - - /// - /// Represents the number of ticks in 1 week. - /// - public const long TicksPerWeek = TimeSpan.TicksPerDay * 7; - - /// - /// Represents the number of ticks in 1 month. - /// - public const long TicksPerMonth = TimeSpan.TicksPerDay * 30; - - /// - /// Represents the number of ticks in 1 year. - /// - public const long TicksPerYear = TimeSpan.TicksPerDay * 365; - - /// - /// Represents the number of ticks in 1 century. - /// - public const long TicksPerCentury = TicksPerYear * 100; - - /// - /// Represents the number of ticks in 1 millenium. - /// - public const long TicksPerMillenium = TicksPerCentury * 10; - - /// - /// A of one minute. - /// - public static readonly TimeSpan Minute1 = TimeSpan.FromMinutes(1); - - /// - /// A of five minutes. - /// - public static readonly TimeSpan Minute5 = TimeSpan.FromMinutes(5); - - /// - /// A of ten minutes. - /// - public static readonly TimeSpan Minute10 = TimeSpan.FromMinutes(10); - - /// - /// A of fifteen minutes. - /// - public static readonly TimeSpan Minute15 = TimeSpan.FromMinutes(15); - - /// - /// A of one hour. - /// - public static readonly TimeSpan Hour = TimeSpan.FromHours(1); - - /// - /// A of one day. - /// - public static readonly TimeSpan Day = TimeSpan.FromDays(1); - - /// - /// A of one week (7 days). - /// - public static readonly TimeSpan Week = TimeSpan.FromTicks(TicksPerWeek); - - /// - /// A of one month (30 days). - /// - public static readonly TimeSpan Month = TimeSpan.FromTicks(TicksPerMonth); - - /// - /// A of one year (365 days). - /// - public static readonly TimeSpan Year = TimeSpan.FromTicks(TicksPerYear); - - /// - /// A that is one tick less than a day. - /// - public static readonly TimeSpan LessOneDay = TimeSpan.FromTicks(TimeSpan.TicksPerDay - 1); - - /// - /// Gets the microseconds component from a . - /// - public static int GetMicroseconds(this TimeSpan ts) - { - return (int)(TicksToMicroseconds(ts.Ticks) % 1000); - } + /// + /// Represents the number of ticks in 1 month. + /// + public const long TicksPerMonth = TimeSpan.TicksPerDay * 30; - /// - /// Gets the microseconds component from a . - /// - public static int GetMicroseconds(this DateTime dt) - { - return (int)(TicksToMicroseconds(dt.Ticks) % 1000); - } + /// + /// Represents the number of ticks in 1 year. + /// + public const long TicksPerYear = TimeSpan.TicksPerDay * 365; - /// - /// Gets the nanoseconds component from a . - /// - public static int GetNanoseconds(this TimeSpan ts) - { - return GetNanoseconds(ts.Ticks); - } + /// + /// Represents the number of ticks in 1 century. + /// + public const long TicksPerCentury = TicksPerYear * 100; - /// - /// Gets the nanoseconds component from a . - /// - public static int GetNanoseconds(this DateTime dt) - { - return GetNanoseconds(dt.Ticks); - } + /// + /// Represents the number of ticks in 1 millenium. + /// + public const long TicksPerMillenium = TicksPerCentury * 10; - /// - /// Gets the nanoseconds component from the specified number of ticks. - /// - public static int GetNanoseconds(this long ticks) - { - return (int)((ticks % 10) * NanosecondsPerTick); - } + /// + /// A of one minute. + /// + public static readonly TimeSpan Minute1 = TimeSpan.FromMinutes(1); - /// - /// Converts a to the total number of nanoseconds. - /// - public static long ToNanoseconds(this TimeSpan ts) - { - return TicksToNanoseconds(ts.Ticks); - } + /// + /// A of five minutes. + /// + public static readonly TimeSpan Minute5 = TimeSpan.FromMinutes(5); - /// - /// Converts a to the total number of nanoseconds. - /// - public static long ToNanoseconds(this DateTime dt) - { - return TicksToNanoseconds(dt.Ticks); - } + /// + /// A of ten minutes. + /// + public static readonly TimeSpan Minute10 = TimeSpan.FromMinutes(10); - /// - /// Converts nanoseconds to ticks. - /// - public static long NanosecondsToTicks(this long nanoseconds) - { - return nanoseconds / NanosecondsPerTick; - } + /// + /// A of fifteen minutes. + /// + public static readonly TimeSpan Minute15 = TimeSpan.FromMinutes(15); - /// - /// Converts ticks to nanoseconds. - /// - public static long TicksToNanoseconds(this long ticks) - { - return checked(ticks * NanosecondsPerTick); - } + /// + /// A of one hour. + /// + public static readonly TimeSpan Hour = TimeSpan.FromHours(1); - /// - /// Adds the specified number of nanoseconds to a . - /// - public static TimeSpan AddNanoseconds(this TimeSpan t, long nanoseconds) - { - return t + TimeSpan.FromTicks(NanosecondsToTicks(nanoseconds)); - } + /// + /// A of one day. + /// + public static readonly TimeSpan Day = TimeSpan.FromDays(1); - /// - /// Adds the specified number of nanoseconds to a . - /// - public static DateTime AddNanoseconds(this DateTime dt, long nanoseconds) - { - return dt.AddTicks(NanosecondsToTicks(nanoseconds)); - } + /// + /// A of one week (7 days). + /// + public static readonly TimeSpan Week = TimeSpan.FromTicks(TicksPerWeek); - /// - /// Adds the specified number of nanoseconds to a . - /// - public static DateTimeOffset AddNanoseconds(this DateTimeOffset dto, long nanoseconds) - { - return dto.AddTicks(NanosecondsToTicks(nanoseconds)); - } + /// + /// A of one month (30 days). + /// + public static readonly TimeSpan Month = TimeSpan.FromTicks(TicksPerMonth); - /// - /// Converts microseconds to ticks. - /// - public static long MicrosecondsToTicks(this long mcs) - { - return mcs * TicksPerMicrosecond; - } + /// + /// A of one year (365 days). + /// + public static readonly TimeSpan Year = TimeSpan.FromTicks(TicksPerYear); - /// - /// Converts ticks to microseconds. - /// - public static long TicksToMicroseconds(this long ticks) - { - return ticks / TicksPerMicrosecond; - } + /// + /// A that is one tick less than a day. + /// + public static readonly TimeSpan LessOneDay = TimeSpan.FromTicks(TimeSpan.TicksPerDay - 1); - /// - /// Adds the specified number of microseconds to a . - /// - public static TimeSpan AddMicroseconds(this TimeSpan t, long microseconds) - { - return t + TimeSpan.FromTicks(MicrosecondsToTicks(microseconds)); - } + /// + /// Gets the microseconds component from a . + /// + public static int GetMicroseconds(this TimeSpan ts) + { + return (int)(TicksToMicroseconds(ts.Ticks) % 1000); + } - /// - /// Adds the specified number of microseconds to a . - /// - public static DateTime AddMicroseconds(this DateTime dt, long microseconds) - { - return dt.AddTicks(MicrosecondsToTicks(microseconds)); - } + /// + /// Gets the microseconds component from a . + /// + public static int GetMicroseconds(this DateTime dt) + { + return (int)(TicksToMicroseconds(dt.Ticks) % 1000); + } - /// - /// Adds the specified number of microseconds to a . - /// - public static DateTimeOffset AddMicroseconds(this DateTimeOffset dto, long microseconds) - { - return dto.AddTicks(MicrosecondsToTicks(microseconds)); - } + /// + /// Gets the nanoseconds component from a . + /// + public static int GetNanoseconds(this TimeSpan ts) + { + return GetNanoseconds(ts.Ticks); + } + + /// + /// Gets the nanoseconds component from a . + /// + public static int GetNanoseconds(this DateTime dt) + { + return GetNanoseconds(dt.Ticks); + } + + /// + /// Gets the nanoseconds component from the specified number of ticks. + /// + public static int GetNanoseconds(this long ticks) + { + return (int)((ticks % 10) * NanosecondsPerTick); + } - /// - /// Truncates a to the specified ticks precision. - /// - public static DateTime Truncate(this DateTime time, long precision) + /// + /// Converts a to the total number of nanoseconds. + /// + public static long ToNanoseconds(this TimeSpan ts) + { + return TicksToNanoseconds(ts.Ticks); + } + + /// + /// Converts a to the total number of nanoseconds. + /// + public static long ToNanoseconds(this DateTime dt) + { + return TicksToNanoseconds(dt.Ticks); + } + + /// + /// Converts nanoseconds to ticks. + /// + public static long NanosecondsToTicks(this long nanoseconds) + { + return nanoseconds / NanosecondsPerTick; + } + + /// + /// Converts ticks to nanoseconds. + /// + public static long TicksToNanoseconds(this long ticks) + { + return checked(ticks * NanosecondsPerTick); + } + + /// + /// Adds the specified number of nanoseconds to a . + /// + public static TimeSpan AddNanoseconds(this TimeSpan t, long nanoseconds) + { + return t + TimeSpan.FromTicks(NanosecondsToTicks(nanoseconds)); + } + + /// + /// Adds the specified number of nanoseconds to a . + /// + public static DateTime AddNanoseconds(this DateTime dt, long nanoseconds) + { + return dt.AddTicks(NanosecondsToTicks(nanoseconds)); + } + + /// + /// Adds the specified number of nanoseconds to a . + /// + public static DateTimeOffset AddNanoseconds(this DateTimeOffset dto, long nanoseconds) + { + return dto.AddTicks(NanosecondsToTicks(nanoseconds)); + } + + /// + /// Converts microseconds to ticks. + /// + public static long MicrosecondsToTicks(this long mcs) + { + return mcs * TicksPerMicrosecond; + } + + /// + /// Converts ticks to microseconds. + /// + public static long TicksToMicroseconds(this long ticks) + { + return ticks / TicksPerMicrosecond; + } + + /// + /// Adds the specified number of microseconds to a . + /// + public static TimeSpan AddMicroseconds(this TimeSpan t, long microseconds) + { + return t + TimeSpan.FromTicks(MicrosecondsToTicks(microseconds)); + } + + /// + /// Adds the specified number of microseconds to a . + /// + public static DateTime AddMicroseconds(this DateTime dt, long microseconds) + { + return dt.AddTicks(MicrosecondsToTicks(microseconds)); + } + + /// + /// Adds the specified number of microseconds to a . + /// + public static DateTimeOffset AddMicroseconds(this DateTimeOffset dto, long microseconds) + { + return dto.AddTicks(MicrosecondsToTicks(microseconds)); + } + + /// + /// Truncates a to the specified ticks precision. + /// + public static DateTime Truncate(this DateTime time, long precision) + { + return time.AddTicks(-(time.Ticks % precision)); + } + + /// + /// Truncates a to the specified time span precision. + /// + public static DateTime Truncate(this DateTime dateTime, TimeSpan timeSpan) + { + return dateTime.Truncate(timeSpan.Ticks); + } + + /// + /// Truncates a to the specified ticks precision. + /// + public static TimeSpan Truncate(this TimeSpan time, long precision) + { + return TimeSpan.FromTicks(time.Ticks - (time.Ticks % precision)); + } + + /// + /// Truncates a to the specified time span precision. + /// + public static TimeSpan Truncate(this TimeSpan dateTime, TimeSpan timeSpan) + { + return dateTime.Truncate(timeSpan.Ticks); + } + + /// + /// Generates a sequence of values from a start to an end with a given interval. + /// + /// Start date. + /// End date. + /// The interval between generated dates. + /// An enumerable of dates. + public static IEnumerable Range(this DateTime from, DateTime to, TimeSpan interval) + { + if (interval <= TimeSpan.Zero) + throw new ArgumentOutOfRangeException(nameof(interval), interval, "Invalid value."); + + while (from <= to) { - return time.AddTicks(-(time.Ticks % precision)); + yield return from; + + from += interval; } + } + + /// + /// Gets the number of days in the month of the given . + /// + public static int DaysInMonth(this DateTime date) + { + return DateTime.DaysInMonth(date.Year, date.Month); + } + + /// + /// Changes the of a . + /// + public static DateTime ChangeKind(this DateTime date, DateTimeKind kind = DateTimeKind.Unspecified) + { + return DateTime.SpecifyKind(date, kind); + } + + /// + /// Converts a to UTC kind. + /// + public static DateTime UtcKind(this DateTime date) + { + return date.ChangeKind(DateTimeKind.Utc); + } + + // http://stackoverflow.com/questions/38039/how-can-i-get-the-datetime-for-the-start-of-the-week + + /// + /// Gets the start of the week for the specified , based on a chosen . + /// + public static DateTime StartOfWeek(this DateTime date, DayOfWeek startOfWeek) + { + var diff = date.DayOfWeek - startOfWeek; + + if (diff < 0) + diff += 7; + + return date.AddDays(-1 * diff).Date; + } + + /// + /// Gets the end of the day for the specified , just before midnight. + /// + public static DateTime EndOfDay(this DateTime dt) + { + return dt.Date + LessOneDay; + } + + /// + /// Gets the end of the day for the specified , just before midnight. + /// + public static DateTimeOffset EndOfDay(this DateTimeOffset dto) + { + return new DateTimeOffset(dto.Date.EndOfDay(), dto.Offset); + } + + /// + /// Represents a reference start date (1/1/1970, UTC). + /// + public static readonly DateTime GregorianStart = new(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + + /// + /// Represents the Eastern Standard Time zone. + /// + public static readonly TimeZoneInfo Est = "Eastern Standard Time".To(); + + /// + /// Represents the Central Standard Time zone. + /// + public static readonly TimeZoneInfo Cst = "Central Standard Time".To(); + + /// + /// Represents the Russian Standard Time zone (Moscow). + /// + public static readonly TimeZoneInfo Moscow = "Russian Standard Time".To(); + + /// + /// Represents the GMT Standard Time zone. + /// + public static readonly TimeZoneInfo Gmt = "GMT Standard Time".To(); + + /// + /// Represents the FLE (Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius) Standard Time zone. + /// + public static readonly TimeZoneInfo Fle = "FLE Standard Time".To(); + + /// + /// Represents the China Standard Time zone. + /// + public static readonly TimeZoneInfo China = "China Standard Time".To(); + + /// + /// Represents the Korea Standard Time zone. + /// + public static readonly TimeZoneInfo Korea = "Korea Standard Time".To(); + + /// + /// Represents the Tokyo Standard Time zone. + /// + public static readonly TimeZoneInfo Tokyo = "Tokyo Standard Time".To(); + + /// + /// Represents the West Central Africa Standard Time zone (Tunisia). + /// + public static readonly TimeZoneInfo Tunisia = "W. Central Africa Standard Time".To(); + + /// + /// Converts a between time zones. + /// + public static DateTime To(this DateTime time, TimeZoneInfo source = null, TimeZoneInfo destination = null) + { + if (source is null) + source = time.Kind == DateTimeKind.Utc ? TimeZoneInfo.Utc : TimeZoneInfo.Local; - /// - /// Truncates a to the specified time span precision. - /// - public static DateTime Truncate(this DateTime dateTime, TimeSpan timeSpan) + return TimeZoneInfo.ConvertTime(time, source, destination ?? TimeZoneInfo.Utc); + } + + /// + /// Attempts to convert a string to a using the provided format. + /// + public static DateTime? TryToDateTime(this string value, string format, CultureInfo ci = null) + { + if (value.IsEmpty()) + return null; + + return value.ToDateTime(format, ci); + } + + /// + /// Converts a string to a using the provided format. + /// + public static DateTime ToDateTime(this string value, string format, CultureInfo ci = null) + { + try { - return dateTime.Truncate(timeSpan.Ticks); + return DateTime.ParseExact(value, format, ci ?? CultureInfo.InvariantCulture); } - - /// - /// Truncates a to the specified ticks precision. - /// - public static TimeSpan Truncate(this TimeSpan time, long precision) + catch (Exception ex) { - return TimeSpan.FromTicks(time.Ticks - (time.Ticks % precision)); + throw new InvalidCastException($"Cannot convert {value} with format {format} to {typeof(DateTime).Name}.", ex); } + } + + /// + /// Formats a to a string using the provided format. + /// + public static string FromDateTime(this DateTime dt, string format, CultureInfo ci = null) + { + return dt.ToString(format, ci ?? CultureInfo.InvariantCulture); + } - /// - /// Truncates a to the specified time span precision. - /// - public static TimeSpan Truncate(this TimeSpan dateTime, TimeSpan timeSpan) + /// + /// Attempts to convert a string to a using the provided format. + /// + public static TimeSpan? TryToTimeSpan(this string value, string format, CultureInfo ci = null) + { + if (value.IsEmpty()) + return null; + + return value.ToTimeSpan(format, ci); + } + + /// + /// Converts a string to a using the provided format. + /// + public static TimeSpan ToTimeSpan(this string value, string format, CultureInfo ci = null) + { + try { - return dateTime.Truncate(timeSpan.Ticks); + return TimeSpan.ParseExact(value, format, ci ?? CultureInfo.InvariantCulture); } - - /// - /// Generates a sequence of values from a start to an end with a given interval. - /// - /// Start date. - /// End date. - /// The interval between generated dates. - /// An enumerable of dates. - public static IEnumerable Range(this DateTime from, DateTime to, TimeSpan interval) + catch (Exception ex) { - if (interval <= TimeSpan.Zero) - throw new ArgumentOutOfRangeException(nameof(interval), interval, "Invalid value."); + throw new InvalidCastException($"Cannot convert {value} with format {format} to {typeof(TimeSpan).Name}.", ex); + } + } - while (from <= to) - { - yield return from; + /// + /// Formats a to a string using the provided format. + /// + public static string FromTimeSpan(this TimeSpan ts, string format, CultureInfo ci = null) + { + return ts.ToString(format, ci ?? CultureInfo.InvariantCulture); + } - from += interval; - } - } + /// + /// Attempts to convert a string to a using the provided format. + /// + public static DateTimeOffset? TryToDateTimeOffset(this string value, string format, CultureInfo ci = null) + { + if (value.IsEmpty()) + return null; - /// - /// Gets the number of days in the month of the given . - /// - public static int DaysInMonth(this DateTime date) - { - return DateTime.DaysInMonth(date.Year, date.Month); - } + return value.ToDateTimeOffset(format, ci); + } - /// - /// Changes the of a . - /// - public static DateTime ChangeKind(this DateTime date, DateTimeKind kind = DateTimeKind.Unspecified) + /// + /// Converts a string to a using the provided format. + /// + public static DateTimeOffset ToDateTimeOffset(this string value, string format, CultureInfo ci = null) + { + try { - return DateTime.SpecifyKind(date, kind); + return DateTimeOffset.ParseExact(value, format, ci ?? CultureInfo.InvariantCulture); } - - /// - /// Converts a to UTC kind. - /// - public static DateTime UtcKind(this DateTime date) + catch (Exception ex) { - return date.ChangeKind(DateTimeKind.Utc); + throw new InvalidCastException($"Cannot convert {value} with format {format} to {typeof(DateTimeOffset).Name}.", ex); } + } - // http://stackoverflow.com/questions/38039/how-can-i-get-the-datetime-for-the-start-of-the-week - - /// - /// Gets the start of the week for the specified , based on a chosen . - /// - public static DateTime StartOfWeek(this DateTime date, DayOfWeek startOfWeek) - { - var diff = date.DayOfWeek - startOfWeek; - - if (diff < 0) - diff += 7; - - return date.AddDays(-1 * diff).Date; - } + /// + /// Converts a and a time zone to a . + /// + public static DateTimeOffset ToDateTimeOffset(this DateTime date, TimeZoneInfo zone) + { + if (zone is null) + throw new ArgumentNullException(nameof(zone)); - /// - /// Gets the end of the day for the specified , just before midnight. - /// - public static DateTime EndOfDay(this DateTime dt) - { - return dt.Date + LessOneDay; - } + return date.ToDateTimeOffset(zone.GetUtcOffset(date)); + } - /// - /// Gets the end of the day for the specified , just before midnight. - /// - public static DateTimeOffset EndOfDay(this DateTimeOffset dto) - { - return new DateTimeOffset(dto.Date.EndOfDay(), dto.Offset); - } + /// + /// Converts a and a specific offset to a . + /// + public static DateTimeOffset ToDateTimeOffset(this DateTime date, TimeSpan offset) + { + return new DateTimeOffset(date.ChangeKind() + offset, offset); + } - /// - /// Represents a reference start date (1/1/1970, UTC). - /// - public static readonly DateTime GregorianStart = new(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); - - /// - /// Represents the Eastern Standard Time zone. - /// - public static readonly TimeZoneInfo Est = "Eastern Standard Time".To(); - - /// - /// Represents the Central Standard Time zone. - /// - public static readonly TimeZoneInfo Cst = "Central Standard Time".To(); - - /// - /// Represents the Russian Standard Time zone (Moscow). - /// - public static readonly TimeZoneInfo Moscow = "Russian Standard Time".To(); - - /// - /// Represents the GMT Standard Time zone. - /// - public static readonly TimeZoneInfo Gmt = "GMT Standard Time".To(); - - /// - /// Represents the FLE (Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius) Standard Time zone. - /// - public static readonly TimeZoneInfo Fle = "FLE Standard Time".To(); - - /// - /// Represents the China Standard Time zone. - /// - public static readonly TimeZoneInfo China = "China Standard Time".To(); - - /// - /// Represents the Korea Standard Time zone. - /// - public static readonly TimeZoneInfo Korea = "Korea Standard Time".To(); - - /// - /// Represents the Tokyo Standard Time zone. - /// - public static readonly TimeZoneInfo Tokyo = "Tokyo Standard Time".To(); - - /// - /// Represents the West Central Africa Standard Time zone (Tunisia). - /// - public static readonly TimeZoneInfo Tunisia = "W. Central Africa Standard Time".To(); - - /// - /// Converts a between time zones. - /// - public static DateTime To(this DateTime time, TimeZoneInfo source = null, TimeZoneInfo destination = null) - { - if (source is null) - source = time.Kind == DateTimeKind.Utc ? TimeZoneInfo.Utc : TimeZoneInfo.Local; + /// + /// Formats a to a string using the provided format. + /// + public static string FromDateTimeOffset(this DateTimeOffset dto, string format, CultureInfo ci = null) + { + return dto.ToString(format, ci ?? CultureInfo.InvariantCulture); + } - return TimeZoneInfo.ConvertTime(time, source, destination ?? TimeZoneInfo.Utc); - } + /// + /// Creates a from a using the local time zone. + /// + public static DateTimeOffset ApplyLocal(this DateTime dt) + { + return dt.ApplyTimeZone(TimeZoneInfo.Local); + } - /// - /// Attempts to convert a string to a using the provided format. - /// - public static DateTime? TryToDateTime(this string value, string format, CultureInfo ci = null) - { - if (value.IsEmpty()) - return null; + /// + /// Creates a from a using UTC. + /// + public static DateTimeOffset ApplyUtc(this DateTime dt) + { + return dt.ApplyTimeZone(TimeZoneInfo.Utc); + } - return value.ToDateTime(format, ci); - } + /// + /// Creates a from a in China Standard Time. + /// + public static DateTimeOffset ApplyChina(this DateTime dt) + { + return dt.ApplyTimeZone(China); + } - /// - /// Converts a string to a using the provided format. - /// - public static DateTime ToDateTime(this string value, string format, CultureInfo ci = null) - { - try - { - return DateTime.ParseExact(value, format, ci ?? CultureInfo.InvariantCulture); - } - catch (Exception ex) - { - throw new InvalidCastException($"Cannot convert {value} with format {format} to {typeof(DateTime).Name}.", ex); - } - } + /// + /// Creates a from a in Eastern Standard Time. + /// + public static DateTimeOffset ApplyEst(this DateTime dt) + { + return dt.ApplyTimeZone(Est); + } - /// - /// Formats a to a string using the provided format. - /// - public static string FromDateTime(this DateTime dt, string format, CultureInfo ci = null) - { - return dt.ToString(format, ci ?? CultureInfo.InvariantCulture); - } + /// + /// Creates a from a in Russian Standard Time (Moscow). + /// + public static DateTimeOffset ApplyMoscow(this DateTime dt) + { + return dt.ApplyTimeZone(Moscow); + } - /// - /// Attempts to convert a string to a using the provided format. - /// - public static TimeSpan? TryToTimeSpan(this string value, string format, CultureInfo ci = null) - { - if (value.IsEmpty()) - return null; + /// + /// Creates a from a in the specified time zone. + /// + public static DateTimeOffset ApplyTimeZone(this DateTime dt, TimeZoneInfo zone) + { + if (zone is null) + throw new ArgumentNullException(nameof(zone)); - return value.ToTimeSpan(format, ci); - } + return dt.ApplyTimeZone(zone.GetUtcOffset(dt.ChangeKind())); + } - /// - /// Converts a string to a using the provided format. - /// - public static TimeSpan ToTimeSpan(this string value, string format, CultureInfo ci = null) + /// + /// Creates a from a with the specified offset. + /// + public static DateTimeOffset ApplyTimeZone(this DateTime dt, TimeSpan offset) + { + try { - try - { - return TimeSpan.ParseExact(value, format, ci ?? CultureInfo.InvariantCulture); - } - catch (Exception ex) - { - throw new InvalidCastException($"Cannot convert {value} with format {format} to {typeof(TimeSpan).Name}.", ex); - } + return new DateTimeOffset(dt.ChangeKind(), offset); } - - /// - /// Formats a to a string using the provided format. - /// - public static string FromTimeSpan(this TimeSpan ts, string format, CultureInfo ci = null) + catch (Exception ex) { - return ts.ToString(format, ci ?? CultureInfo.InvariantCulture); + throw new ArgumentException($"Cannot convert {dt} to {nameof(DateTimeOffset)}.", nameof(dt), ex); } + } - /// - /// Attempts to convert a string to a using the provided format. - /// - public static DateTimeOffset? TryToDateTimeOffset(this string value, string format, CultureInfo ci = null) - { - if (value.IsEmpty()) - return null; + /// + /// Converts a to a in the specified time zone. + /// + public static DateTime ToLocalTime(this DateTimeOffset dto, TimeZoneInfo zone) + { + return dto.Convert(zone).DateTime; + } - return value.ToDateTimeOffset(format, ci); - } + /// + /// Converts a to China Standard Time zone. + /// + public static DateTimeOffset ConvertToChina(this DateTimeOffset dto) + { + return TimeZoneInfo.ConvertTime(dto, China); + } - /// - /// Converts a string to a using the provided format. - /// - public static DateTimeOffset ToDateTimeOffset(this string value, string format, CultureInfo ci = null) - { - try - { - return DateTimeOffset.ParseExact(value, format, ci ?? CultureInfo.InvariantCulture); - } - catch (Exception ex) - { - throw new InvalidCastException($"Cannot convert {value} with format {format} to {typeof(DateTimeOffset).Name}.", ex); - } - } + /// + /// Converts the specified to Eastern Standard Time. + /// + /// The to convert. + /// A new in Eastern Standard Time. + public static DateTimeOffset ConvertToEst(this DateTimeOffset dto) + { + return TimeZoneInfo.ConvertTime(dto, Est); + } - /// - /// Converts a and a time zone to a . - /// - public static DateTimeOffset ToDateTimeOffset(this DateTime date, TimeZoneInfo zone) - { - if (zone is null) - throw new ArgumentNullException(nameof(zone)); + /// + /// Converts the specified to Moscow Time. + /// + /// The to convert. + /// A new in Moscow Time. + public static DateTimeOffset ConvertToMoscow(this DateTimeOffset dto) + { + return TimeZoneInfo.ConvertTime(dto, Moscow); + } - return date.ToDateTimeOffset(zone.GetUtcOffset(date)); - } + /// + /// Converts the specified to UTC. + /// + /// The to convert. + /// A new in UTC. + public static DateTimeOffset ConvertToUtc(this DateTimeOffset dto) + { + return TimeZoneInfo.ConvertTime(dto, TimeZoneInfo.Utc); + } - /// - /// Converts a and a specific offset to a . - /// - public static DateTimeOffset ToDateTimeOffset(this DateTime date, TimeSpan offset) - { - return new DateTimeOffset(date.ChangeKind() + offset, offset); - } + /// + /// Converts the specified to the provided time zone. + /// + /// The to convert. + /// The target . + /// A in the specified time zone. + public static DateTimeOffset Convert(this DateTimeOffset dto, TimeZoneInfo zone) + { + return TimeZoneInfo.ConvertTime(dto, zone); + } - /// - /// Formats a to a string using the provided format. - /// - public static string FromDateTimeOffset(this DateTimeOffset dto, string format, CultureInfo ci = null) - { - return dto.ToString(format, ci ?? CultureInfo.InvariantCulture); - } + /// + /// Truncates the specified to the given . + /// + /// The to truncate. + /// The precision. + /// The truncated . + public static DateTimeOffset Truncate(this DateTimeOffset time, TimeSpan timeSpan) + { + return time.Truncate(timeSpan.Ticks); + } - /// - /// Creates a from a using the local time zone. - /// - public static DateTimeOffset ApplyLocal(this DateTime dt) - { - return dt.ApplyTimeZone(TimeZoneInfo.Local); - } + /// + /// Truncates the specified to the given precision in ticks. + /// + /// The to truncate. + /// The precision in ticks. + /// The truncated . + public static DateTimeOffset Truncate(this DateTimeOffset time, long precision) + { + var offset = time.Offset; + return new DateTimeOffset(time.UtcDateTime.Truncate(precision).ChangeKind() + offset, offset); + } - /// - /// Creates a from a using UTC. - /// - public static DateTimeOffset ApplyUtc(this DateTime dt) - { - return dt.ApplyTimeZone(TimeZoneInfo.Utc); - } + /// + /// Parses the specified string as an ISO8601 date/time. + /// + /// The string to parse. + /// An optional . + /// A parsed from the string, in UTC. + public static DateTime FromIso8601(this string str, IFormatProvider provider = null) + { + return DateTime.Parse(str, provider, DateTimeStyles.RoundtripKind).UtcKind(); + } - /// - /// Creates a from a in China Standard Time. - /// - public static DateTimeOffset ApplyChina(this DateTime dt) - { - return dt.ApplyTimeZone(China); - } + /// + /// Formats the specified as an ISO8601 string. + /// + /// The to format. + /// An optional . + /// An ISO8601-formatted string. + public static string ToIso8601(this DateTime dt, IFormatProvider provider = null) + { + return dt.ToString("yyyy-MM-dd'T'HH:mm:ss.fffK", provider); + } - /// - /// Creates a from a in Eastern Standard Time. - /// - public static DateTimeOffset ApplyEst(this DateTime dt) - { - return dt.ApplyTimeZone(Est); - } + // https://stackoverflow.com/questions/11154673/get-the-correct-week-number-of-a-given-date - /// - /// Creates a from a in Russian Standard Time (Moscow). - /// - public static DateTimeOffset ApplyMoscow(this DateTime dt) - { - return dt.ApplyTimeZone(Moscow); - } + /// + /// Calculates the ISO8601 week number of the specified date. + /// + /// The to evaluate. + /// An optional . + /// The ISO8601 week of the year. + public static int GetIso8601WeekOfYear(this DateTime time, CultureInfo ci = null) + { + // Seriously cheat. If its Monday, Tuesday or Wednesday, then it'll + // be the same week# as whatever Thursday, Friday or Saturday are, + // and we always get those right + var calendar = (ci ?? CultureInfo.InvariantCulture).Calendar; - /// - /// Creates a from a in the specified time zone. - /// - public static DateTimeOffset ApplyTimeZone(this DateTime dt, TimeZoneInfo zone) + var day = calendar.GetDayOfWeek(time); + if (day >= DayOfWeek.Monday && day <= DayOfWeek.Wednesday) { - if (zone is null) - throw new ArgumentNullException(nameof(zone)); - - return dt.ApplyTimeZone(zone.GetUtcOffset(dt.ChangeKind())); + time = time.AddDays(3); } - /// - /// Creates a from a with the specified offset. - /// - public static DateTimeOffset ApplyTimeZone(this DateTime dt, TimeSpan offset) - { - try - { - return new DateTimeOffset(dt.ChangeKind(), offset); - } - catch (Exception ex) - { - throw new ArgumentException($"Cannot convert {dt} to {nameof(DateTimeOffset)}.", nameof(dt), ex); - } - } + // Return the week of our adjusted day + return calendar.GetWeekOfYear(time, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday); + } - /// - /// Converts a to a in the specified time zone. - /// - public static DateTime ToLocalTime(this DateTimeOffset dto, TimeZoneInfo zone) - { - return dto.Convert(zone).DateTime; - } + /// + /// Converts the specified to Unix time in seconds or milliseconds. + /// + /// The to convert. + /// If set to true, returns seconds; otherwise milliseconds. + /// A double representing the Unix time. + public static double ToUnix(this DateTimeOffset time, bool isSeconds = true) + { + return time.UtcDateTime.ToUnix(isSeconds); + } - /// - /// Converts a to China Standard Time zone. - /// - public static DateTimeOffset ConvertToChina(this DateTimeOffset dto) - { - return TimeZoneInfo.ConvertTime(dto, China); - } + /// + /// Converts the specified to Unix time in seconds or milliseconds. + /// + /// The to convert. + /// If set to true, returns seconds; otherwise milliseconds. + /// A double representing the Unix time. + public static double ToUnix(this DateTime time, bool isSeconds = true) + { + var diff = time.GetUnixDiff(); - /// - /// Converts the specified to Eastern Standard Time. - /// - /// The to convert. - /// A new in Eastern Standard Time. - public static DateTimeOffset ConvertToEst(this DateTimeOffset dto) - { - return TimeZoneInfo.ConvertTime(dto, Est); - } + return isSeconds ? diff.TotalSeconds : diff.TotalMilliseconds; + } - /// - /// Converts the specified to Moscow Time. - /// - /// The to convert. - /// A new in Moscow Time. - public static DateTimeOffset ConvertToMoscow(this DateTimeOffset dto) + /// + /// Gets the difference between the specified and the . + /// + /// The to evaluate. + /// A representing the difference. + /// Thrown if the time is earlier than . + public static TimeSpan GetUnixDiff(this DateTime time) + { + if (time.Kind != DateTimeKind.Utc) { - return TimeZoneInfo.ConvertTime(dto, Moscow); + time = time.ToUniversalTime(); + //throw new ArgumentException(nameof(time)); } - /// - /// Converts the specified to UTC. - /// - /// The to convert. - /// A new in UTC. - public static DateTimeOffset ConvertToUtc(this DateTimeOffset dto) - { - return TimeZoneInfo.ConvertTime(dto, TimeZoneInfo.Utc); - } + var diff = time - GregorianStart; - /// - /// Converts the specified to the provided time zone. - /// - /// The to convert. - /// The target . - /// A in the specified time zone. - public static DateTimeOffset Convert(this DateTimeOffset dto, TimeZoneInfo zone) - { - return TimeZoneInfo.ConvertTime(dto, zone); - } + if (diff < TimeSpan.Zero) + throw new ArgumentOutOfRangeException(nameof(time)); - /// - /// Truncates the specified to the given . - /// - /// The to truncate. - /// The precision. - /// The truncated . - public static DateTimeOffset Truncate(this DateTimeOffset time, TimeSpan timeSpan) - { - return time.Truncate(timeSpan.Ticks); - } + return diff; + } - /// - /// Truncates the specified to the given precision in ticks. - /// - /// The to truncate. - /// The precision in ticks. - /// The truncated . - public static DateTimeOffset Truncate(this DateTimeOffset time, long precision) - { - var offset = time.Offset; - return new DateTimeOffset(time.UtcDateTime.Truncate(precision).ChangeKind() + offset, offset); - } + /// + /// Creates a from a Unix time in seconds or milliseconds. + /// + /// The Unix time to convert. + /// If set to true, interprets time as seconds; otherwise milliseconds. + /// A in UTC. + public static DateTime FromUnix(this long time, bool isSeconds = true) + { + return isSeconds ? GregorianStart.AddSeconds(time) : GregorianStart.AddMilliseconds(time); + } - /// - /// Parses the specified string as an ISO8601 date/time. - /// - /// The string to parse. - /// An optional . - /// A parsed from the string, in UTC. - public static DateTime FromIso8601(this string str, IFormatProvider provider = null) - { - return DateTime.Parse(str, provider, DateTimeStyles.RoundtripKind).UtcKind(); - } + /// + /// Creates a from a Unix time in seconds or milliseconds. + /// + /// The Unix time to convert. + /// If set to true, interprets time as seconds; otherwise milliseconds. + /// A in UTC. + public static DateTime FromUnix(this double time, bool isSeconds = true) + { + return isSeconds ? GregorianStart.AddSeconds(time) : GregorianStart.AddMilliseconds(time); + } - /// - /// Formats the specified as an ISO8601 string. - /// - /// The to format. - /// An optional . - /// An ISO8601-formatted string. - public static string ToIso8601(this DateTime dt, IFormatProvider provider = null) - { - return dt.ToString("yyyy-MM-dd'T'HH:mm:ss.fffK", provider); - } + /// + /// Tries to create a from a Unix time in seconds or milliseconds. Returns null if time is 0. + /// + /// The Unix time to convert. + /// If set to true, interprets time as seconds; otherwise milliseconds. + /// A in UTC, or null if 0. + public static DateTime? TryFromUnix(this long time, bool isSeconds = true) + { + if (time == 0) + return null; - // https://stackoverflow.com/questions/11154673/get-the-correct-week-number-of-a-given-date + return time.FromUnix(isSeconds); + } - /// - /// Calculates the ISO8601 week number of the specified date. - /// - /// The to evaluate. - /// An optional . - /// The ISO8601 week of the year. - public static int GetIso8601WeekOfYear(this DateTime time, CultureInfo ci = null) - { - // Seriously cheat. If its Monday, Tuesday or Wednesday, then it'll - // be the same week# as whatever Thursday, Friday or Saturday are, - // and we always get those right - var calendar = (ci ?? CultureInfo.InvariantCulture).Calendar; - - var day = calendar.GetDayOfWeek(time); - if (day >= DayOfWeek.Monday && day <= DayOfWeek.Wednesday) - { - time = time.AddDays(3); - } - - // Return the week of our adjusted day - return calendar.GetWeekOfYear(time, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday); - } + /// + /// Tries to create a from a Unix time in seconds or milliseconds. Returns null if time is near zero. + /// + /// The Unix time to convert. + /// If set to true, interprets time as seconds; otherwise milliseconds. + /// A in UTC, or null if near zero. + public static DateTime? TryFromUnix(this double time, bool isSeconds = true) + { + if (Math.Abs(time) < double.Epsilon) + return null; - /// - /// Converts the specified to Unix time in seconds or milliseconds. - /// - /// The to convert. - /// If set to true, returns seconds; otherwise milliseconds. - /// A double representing the Unix time. - public static double ToUnix(this DateTimeOffset time, bool isSeconds = true) - { - return time.UtcDateTime.ToUnix(isSeconds); - } + return time.FromUnix(isSeconds); + } - /// - /// Converts the specified to Unix time in seconds or milliseconds. - /// - /// The to convert. - /// If set to true, returns seconds; otherwise milliseconds. - /// A double representing the Unix time. - public static double ToUnix(this DateTime time, bool isSeconds = true) - { - var diff = time.GetUnixDiff(); + /// + /// Creates a from Microseconds since . + /// + /// Microseconds to convert. + /// A in UTC. + public static DateTime FromUnixMcs(this long mcs) + { + return GregorianStart.AddMicroseconds(mcs); + } - return isSeconds ? diff.TotalSeconds : diff.TotalMilliseconds; - } + /// + /// Creates a from Microseconds since . + /// + /// Microseconds to convert. + /// A in UTC. + public static DateTime FromUnixMcs(this double mcs) + { + return FromUnixMcs((long)mcs); + } - /// - /// Gets the difference between the specified and the . - /// - /// The to evaluate. - /// A representing the difference. - /// Thrown if the time is earlier than . - public static TimeSpan GetUnixDiff(this DateTime time) - { - if (time.Kind != DateTimeKind.Utc) - { - time = time.ToUniversalTime(); - //throw new ArgumentException(nameof(time)); - } + /// + /// Converts the specified to Unix Microseconds since . + /// + /// The to convert. + /// A long representing the Unix time in microseconds. + public static long ToUnixMcs(this DateTime time) + { + return time.GetUnixDiff().Ticks.TicksToMicroseconds(); + } - var diff = time - GregorianStart; + /// + /// Gets the current Unix time in seconds. + /// + public static double UnixNowS => DateTime.UtcNow.ToUnix(); - if (diff < TimeSpan.Zero) - throw new ArgumentOutOfRangeException(nameof(time)); + /// + /// Gets the current Unix time in milliseconds. + /// + public static double UnixNowMls => DateTime.UtcNow.ToUnix(false); - return diff; - } + //private const string _timeFormat = "yyyy-MM-dd'T'HH:mm:ss.fffffff'Z'"; // "yyyy-MM-dd'T'HH:mm:ss.fffffffff'Z'" - /// - /// Creates a from a Unix time in seconds or milliseconds. - /// - /// The Unix time to convert. - /// If set to true, interprets time as seconds; otherwise milliseconds. - /// A in UTC. - public static DateTime FromUnix(this long time, bool isSeconds = true) - { - return isSeconds ? GregorianStart.AddSeconds(time) : GregorianStart.AddMilliseconds(time); - } + //public static string ToRfc3339(this DateTimeOffset time) + //{ + // var str = time.ToString(_timeFormat); + // return str.Insert(str.IndexOf('.') + 8, "00"); + //} - /// - /// Creates a from a Unix time in seconds or milliseconds. - /// - /// The Unix time to convert. - /// If set to true, interprets time as seconds; otherwise milliseconds. - /// A in UTC. - public static DateTime FromUnix(this double time, bool isSeconds = true) - { - return isSeconds ? GregorianStart.AddSeconds(time) : GregorianStart.AddMilliseconds(time); - } + //public static DateTimeOffset FromRfc3339(this string time) + //{ + // if (time.IsEmpty()) + // throw new ArgumentNullException(nameof(time)); - /// - /// Tries to create a from a Unix time in seconds or milliseconds. Returns null if time is 0. - /// - /// The Unix time to convert. - /// If set to true, interprets time as seconds; otherwise milliseconds. - /// A in UTC, or null if 0. - public static DateTime? TryFromUnix(this long time, bool isSeconds = true) - { - if (time == 0) - return null; + // // cannot parse nanoseconds + // var dt = time.Remove(time.IndexOf('.') + 8, 2).ToDateTime(_timeFormat); + // //var dt = time.ToDateTime(_timeFormat); + // return dt.ChangeKind(DateTimeKind.Utc); + //} - return time.FromUnix(isSeconds); - } + /// + /// Checks if the given is a date/time type. + /// + /// The to check. + /// True if the type is a or ; otherwise false. + /// Thrown if is null. + public static bool IsDateTime(this Type type) + { + if (type is null) + throw new ArgumentNullException(nameof(type)); - /// - /// Tries to create a from a Unix time in seconds or milliseconds. Returns null if time is near zero. - /// - /// The Unix time to convert. - /// If set to true, interprets time as seconds; otherwise milliseconds. - /// A in UTC, or null if near zero. - public static DateTime? TryFromUnix(this double time, bool isSeconds = true) - { - if (Math.Abs(time) < double.Epsilon) - return null; + return type == typeof(DateTimeOffset) || type == typeof(DateTime); + } - return time.FromUnix(isSeconds); - } + /// + /// Checks if the given is a date or time type. + /// + /// The to check. + /// True if the type is a date/time or ; otherwise false. + public static bool IsDateOrTime(this Type type) + { + return type.IsDateTime() || type == typeof(TimeSpan); + } - /// - /// Creates a from Microseconds since . - /// - /// Microseconds to convert. - /// A in UTC. - public static DateTime FromUnixMcs(this long mcs) - { - return GregorianStart.AddMicroseconds(mcs); - } + /// + /// Checks if the given falls on a weekday (Monday through Friday). + /// + /// The to check. + /// True if it is a weekday; otherwise false. + public static bool IsWeekday(this DateTimeOffset date) + => date.DayOfWeek.IsWeekday(); - /// - /// Creates a from Microseconds since . - /// - /// Microseconds to convert. - /// A in UTC. - public static DateTime FromUnixMcs(this double mcs) - { - return FromUnixMcs((long)mcs); - } + /// + /// Checks if the given falls on a weekend (Saturday or Sunday). + /// + /// The to check. + /// True if it is a weekend; otherwise false. + public static bool IsWeekend(this DateTimeOffset date) + => date.DayOfWeek.IsWeekend(); - /// - /// Converts the specified to Unix Microseconds since . - /// - /// The to convert. - /// A long representing the Unix time in microseconds. - public static long ToUnixMcs(this DateTime time) - { - return time.GetUnixDiff().Ticks.TicksToMicroseconds(); - } + /// + /// Checks if the given falls on a weekday (Monday through Friday). + /// + /// The to check. + /// True if it is a weekday; otherwise false. + public static bool IsWeekday(this DateTime date) + => date.DayOfWeek.IsWeekday(); - /// - /// Gets the current Unix time in seconds. - /// - public static double UnixNowS => DateTime.UtcNow.ToUnix(); - - /// - /// Gets the current Unix time in milliseconds. - /// - public static double UnixNowMls => DateTime.UtcNow.ToUnix(false); - - //private const string _timeFormat = "yyyy-MM-dd'T'HH:mm:ss.fffffff'Z'"; // "yyyy-MM-dd'T'HH:mm:ss.fffffffff'Z'" - - //public static string ToRfc3339(this DateTimeOffset time) - //{ - // var str = time.ToString(_timeFormat); - // return str.Insert(str.IndexOf('.') + 8, "00"); - //} - - //public static DateTimeOffset FromRfc3339(this string time) - //{ - // if (time.IsEmpty()) - // throw new ArgumentNullException(nameof(time)); - - // // cannot parse nanoseconds - // var dt = time.Remove(time.IndexOf('.') + 8, 2).ToDateTime(_timeFormat); - // //var dt = time.ToDateTime(_timeFormat); - // return dt.ChangeKind(DateTimeKind.Utc); - //} - - /// - /// Checks if the given is a date/time type. - /// - /// The to check. - /// True if the type is a or ; otherwise false. - /// Thrown if is null. - public static bool IsDateTime(this Type type) - { - if (type is null) - throw new ArgumentNullException(nameof(type)); + /// + /// Checks if the given falls on a weekend (Saturday or Sunday). + /// + /// The to check. + /// True if it is a weekend; otherwise false. + public static bool IsWeekend(this DateTime date) + => date.DayOfWeek.IsWeekend(); - return type == typeof(DateTimeOffset) || type == typeof(DateTime); - } + /// + /// Checks if the given represents a weekday (Monday through Friday). + /// + /// The . + /// True if it is a weekday; otherwise false. + public static bool IsWeekday(this DayOfWeek dow) + => !dow.IsWeekend(); - /// - /// Checks if the given is a date or time type. - /// - /// The to check. - /// True if the type is a date/time or ; otherwise false. - public static bool IsDateOrTime(this Type type) - { - return type.IsDateTime() || type == typeof(TimeSpan); - } + /// + /// Checks if the given represents a weekend (Saturday or Sunday). + /// + /// The . + /// True if it is a weekend; otherwise false. + public static bool IsWeekend(this DayOfWeek dow) + => dow == DayOfWeek.Saturday || dow == DayOfWeek.Sunday; - /// - /// Checks if the given falls on a weekday (Monday through Friday). - /// - /// The to check. - /// True if it is a weekday; otherwise false. - public static bool IsWeekday(this DateTimeOffset date) - => date.DayOfWeek.IsWeekday(); - - /// - /// Checks if the given falls on a weekend (Saturday or Sunday). - /// - /// The to check. - /// True if it is a weekend; otherwise false. - public static bool IsWeekend(this DateTimeOffset date) - => date.DayOfWeek.IsWeekend(); - - /// - /// Checks if the given falls on a weekday (Monday through Friday). - /// - /// The to check. - /// True if it is a weekday; otherwise false. - public static bool IsWeekday(this DateTime date) - => date.DayOfWeek.IsWeekday(); - - /// - /// Checks if the given falls on a weekend (Saturday or Sunday). - /// - /// The to check. - /// True if it is a weekend; otherwise false. - public static bool IsWeekend(this DateTime date) - => date.DayOfWeek.IsWeekend(); - - /// - /// Checks if the given represents a weekday (Monday through Friday). - /// - /// The . - /// True if it is a weekday; otherwise false. - public static bool IsWeekday(this DayOfWeek dow) - => !dow.IsWeekend(); - - /// - /// Checks if the given represents a weekend (Saturday or Sunday). - /// - /// The . - /// True if it is a weekend; otherwise false. - public static bool IsWeekend(this DayOfWeek dow) - => dow == DayOfWeek.Saturday || dow == DayOfWeek.Sunday; - - /// - /// Determines the lunar phase for the specified date. - /// - /// The for which to determine the phase. - /// A value representing the phase of the moon. - public static LunarPhases GetLunarPhase(this DateTime date) - { - // Convert the date to Julian Date - var julianDate = ToJulianDate(date); + /// + /// Determines the lunar phase for the specified date. + /// + /// The for which to determine the phase. + /// A value representing the phase of the moon. + public static LunarPhases GetLunarPhase(this DateTime date) + { + // Convert the date to Julian Date + var julianDate = ToJulianDate(date); - // Calculate days since the last known new moon (Jan 6, 2000) - var daysSinceNew = julianDate - 2451549.5; + // Calculate days since the last known new moon (Jan 6, 2000) + var daysSinceNew = julianDate - 2451549.5; - // Calculate the number of lunar cycles since the reference date - var newMoons = daysSinceNew / 29.53; // 29.53 is the length of a lunar cycle in days + // Calculate the number of lunar cycles since the reference date + var newMoons = daysSinceNew / 29.53; // 29.53 is the length of a lunar cycle in days - // Get the current position in the lunar cycle (0 to 1) - var phase = newMoons - Math.Floor(newMoons); + // Get the current position in the lunar cycle (0 to 1) + var phase = newMoons - Math.Floor(newMoons); - // Convert the phase (0 to 1) to one of 8 moon phases (0 to 7) - var phaseIndex = (LunarPhases)Math.Floor(phase * 8); + // Convert the phase (0 to 1) to one of 8 moon phases (0 to 7) + var phaseIndex = (LunarPhases)Math.Floor(phase * 8); - return phaseIndex; - } + return phaseIndex; + } - /// - /// Converts the specified to a Julian date. - /// - /// The to convert. - /// A double representing the Julian date. - public static double ToJulianDate(this DateTime date) - { - return date.ToOADate() + 2415018.5; - } + /// + /// Converts the specified to a Julian date. + /// + /// The to convert. + /// A double representing the Julian date. + public static double ToJulianDate(this DateTime date) + { + return date.ToOADate() + 2415018.5; } } \ No newline at end of file diff --git a/Common/TupleHelper.cs b/Common/TupleHelper.cs index 7a22664b..1a9bd33e 100644 --- a/Common/TupleHelper.cs +++ b/Common/TupleHelper.cs @@ -1,327 +1,326 @@ -namespace Ecng.Common +namespace Ecng.Common; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; + +/// +/// Provides helper methods for working with tuple types. +/// +public static class TupleHelper { - using System; - using System.Collections.Generic; - using System.Linq; - using System.Runtime.CompilerServices; + private static readonly HashSet _tupleTypes = + [ + typeof(Tuple<>), + typeof(Tuple<,>), + typeof(Tuple<,,>), + typeof(Tuple<,,,>), + typeof(Tuple<,,,,>), + typeof(Tuple<,,,,,>), + typeof(Tuple<,,,,,,>), + typeof(Tuple<,,,,,,,>), + + typeof(ValueTuple<>), + typeof(ValueTuple<,>), + typeof(ValueTuple<,,>), + typeof(ValueTuple<,,,>), + typeof(ValueTuple<,,,,>), + typeof(ValueTuple<,,,,,>), + typeof(ValueTuple<,,,,,,>), + typeof(ValueTuple<,,,,,,,>), + ]; /// - /// Provides helper methods for working with tuple types. + /// Determines whether the specified type is a tuple type. /// - public static class TupleHelper + /// The type to check. + /// true if the specified type is a tuple; otherwise, false. + /// Thrown when is null. + public static bool IsTuple(this Type tupleType) { - private static readonly HashSet _tupleTypes = - [ - typeof(Tuple<>), - typeof(Tuple<,>), - typeof(Tuple<,,>), - typeof(Tuple<,,,>), - typeof(Tuple<,,,,>), - typeof(Tuple<,,,,,>), - typeof(Tuple<,,,,,,>), - typeof(Tuple<,,,,,,,>), + if (tupleType is null) + throw new ArgumentNullException(nameof(tupleType)); - typeof(ValueTuple<>), - typeof(ValueTuple<,>), - typeof(ValueTuple<,,>), - typeof(ValueTuple<,,,>), - typeof(ValueTuple<,,,,>), - typeof(ValueTuple<,,,,,>), - typeof(ValueTuple<,,,,,,>), - typeof(ValueTuple<,,,,,,,>), - ]; + return tupleType.IsGenericType && _tupleTypes.Contains(tupleType.GetGenericTypeDefinition()); + } - /// - /// Determines whether the specified type is a tuple type. - /// - /// The type to check. - /// true if the specified type is a tuple; otherwise, false. - /// Thrown when is null. - public static bool IsTuple(this Type tupleType) - { - if (tupleType is null) - throw new ArgumentNullException(nameof(tupleType)); + /// + /// Returns the values of a as an enumerable collection. + /// + /// The type of the tuple element. + /// The tuple instance. + /// An enumerable collection of tuple element values. + public static IEnumerable ToValues(this Tuple tuple) + { + yield return tuple.Item1; + } - return tupleType.IsGenericType && _tupleTypes.Contains(tupleType.GetGenericTypeDefinition()); - } + /// + /// Returns the values of a as an enumerable collection. + /// + /// The type of the first element. + /// The type of the second element. + /// The tuple instance. + /// An enumerable collection of tuple element values. + public static IEnumerable ToValues(this Tuple tuple) + { + yield return tuple.Item1; + yield return tuple.Item2; + } - /// - /// Returns the values of a as an enumerable collection. - /// - /// The type of the tuple element. - /// The tuple instance. - /// An enumerable collection of tuple element values. - public static IEnumerable ToValues(this Tuple tuple) - { - yield return tuple.Item1; - } + /// + /// Returns the values of a as an enumerable collection. + /// + /// The type of the first element. + /// The type of the second element. + /// The type of the third element. + /// The tuple instance. + /// An enumerable collection of tuple element values. + public static IEnumerable ToValues(this Tuple tuple) + { + yield return tuple.Item1; + yield return tuple.Item2; + yield return tuple.Item3; + } - /// - /// Returns the values of a as an enumerable collection. - /// - /// The type of the first element. - /// The type of the second element. - /// The tuple instance. - /// An enumerable collection of tuple element values. - public static IEnumerable ToValues(this Tuple tuple) - { - yield return tuple.Item1; - yield return tuple.Item2; - } + /// + /// Returns the values of a as an enumerable collection. + /// + /// The type of the first element. + /// The type of the second element. + /// The type of the third element. + /// The type of the fourth element. + /// The tuple instance. + /// An enumerable collection of tuple element values. + public static IEnumerable ToValues(this Tuple tuple) + { + yield return tuple.Item1; + yield return tuple.Item2; + yield return tuple.Item3; + yield return tuple.Item4; + } - /// - /// Returns the values of a as an enumerable collection. - /// - /// The type of the first element. - /// The type of the second element. - /// The type of the third element. - /// The tuple instance. - /// An enumerable collection of tuple element values. - public static IEnumerable ToValues(this Tuple tuple) - { - yield return tuple.Item1; - yield return tuple.Item2; - yield return tuple.Item3; - } + /// + /// Returns the values of a as an enumerable collection. + /// + /// The type of the first element. + /// The type of the second element. + /// The type of the third element. + /// The type of the fourth element. + /// The type of the fifth element. + /// The tuple instance. + /// An enumerable collection of tuple element values. + public static IEnumerable ToValues(this Tuple tuple) + { + yield return tuple.Item1; + yield return tuple.Item2; + yield return tuple.Item3; + yield return tuple.Item4; + yield return tuple.Item5; + } - /// - /// Returns the values of a as an enumerable collection. - /// - /// The type of the first element. - /// The type of the second element. - /// The type of the third element. - /// The type of the fourth element. - /// The tuple instance. - /// An enumerable collection of tuple element values. - public static IEnumerable ToValues(this Tuple tuple) - { - yield return tuple.Item1; - yield return tuple.Item2; - yield return tuple.Item3; - yield return tuple.Item4; - } + /// + /// Returns the values of a as an enumerable collection. + /// + /// The type of the first element. + /// The type of the second element. + /// The type of the third element. + /// The type of the fourth element. + /// The type of the fifth element. + /// The type of the sixth element. + /// The tuple instance. + /// An enumerable collection of tuple element values. + public static IEnumerable ToValues(this Tuple tuple) + { + yield return tuple.Item1; + yield return tuple.Item2; + yield return tuple.Item3; + yield return tuple.Item4; + yield return tuple.Item5; + yield return tuple.Item6; + } - /// - /// Returns the values of a as an enumerable collection. - /// - /// The type of the first element. - /// The type of the second element. - /// The type of the third element. - /// The type of the fourth element. - /// The type of the fifth element. - /// The tuple instance. - /// An enumerable collection of tuple element values. - public static IEnumerable ToValues(this Tuple tuple) - { - yield return tuple.Item1; - yield return tuple.Item2; - yield return tuple.Item3; - yield return tuple.Item4; - yield return tuple.Item5; - } + /// + /// Returns the values of a as an enumerable collection. + /// + /// The type of the tuple element. + /// The tuple instance. + /// An enumerable collection of tuple element values. + public static IEnumerable ToValues(this ValueTuple tuple) + { + yield return tuple.Item1; + } - /// - /// Returns the values of a as an enumerable collection. - /// - /// The type of the first element. - /// The type of the second element. - /// The type of the third element. - /// The type of the fourth element. - /// The type of the fifth element. - /// The type of the sixth element. - /// The tuple instance. - /// An enumerable collection of tuple element values. - public static IEnumerable ToValues(this Tuple tuple) - { - yield return tuple.Item1; - yield return tuple.Item2; - yield return tuple.Item3; - yield return tuple.Item4; - yield return tuple.Item5; - yield return tuple.Item6; - } + /// + /// Returns the values of a as an enumerable collection. + /// + /// The type of the first element. + /// The type of the second element. + /// The tuple instance. + /// An enumerable collection of tuple element values. + public static IEnumerable ToValues(this ValueTuple tuple) + { + yield return tuple.Item1; + yield return tuple.Item2; + } - /// - /// Returns the values of a as an enumerable collection. - /// - /// The type of the tuple element. - /// The tuple instance. - /// An enumerable collection of tuple element values. - public static IEnumerable ToValues(this ValueTuple tuple) - { - yield return tuple.Item1; - } + /// + /// Returns the values of a as an enumerable collection. + /// + /// The type of the first element. + /// The type of the second element. + /// The type of the third element. + /// The tuple instance. + /// An enumerable collection of tuple element values. + public static IEnumerable ToValues(this ValueTuple tuple) + { + yield return tuple.Item1; + yield return tuple.Item2; + yield return tuple.Item3; + } - /// - /// Returns the values of a as an enumerable collection. - /// - /// The type of the first element. - /// The type of the second element. - /// The tuple instance. - /// An enumerable collection of tuple element values. - public static IEnumerable ToValues(this ValueTuple tuple) - { - yield return tuple.Item1; - yield return tuple.Item2; - } + /// + /// Returns the values of a as an enumerable collection. + /// + /// The type of the first element. + /// The type of the second element. + /// The type of the third element. + /// The type of the fourth element. + /// The tuple instance. + /// An enumerable collection of tuple element values. + public static IEnumerable ToValues(this ValueTuple tuple) + { + yield return tuple.Item1; + yield return tuple.Item2; + yield return tuple.Item3; + yield return tuple.Item4; + } - /// - /// Returns the values of a as an enumerable collection. - /// - /// The type of the first element. - /// The type of the second element. - /// The type of the third element. - /// The tuple instance. - /// An enumerable collection of tuple element values. - public static IEnumerable ToValues(this ValueTuple tuple) - { - yield return tuple.Item1; - yield return tuple.Item2; - yield return tuple.Item3; - } + /// + /// Returns the values of a as an enumerable collection. + /// + /// The type of the first element. + /// The type of the second element. + /// The type of the third element. + /// The type of the fourth element. + /// The type of the fifth element. + /// The tuple instance. + /// An enumerable collection of tuple element values. + public static IEnumerable ToValues(this ValueTuple tuple) + { + yield return tuple.Item1; + yield return tuple.Item2; + yield return tuple.Item3; + yield return tuple.Item4; + yield return tuple.Item5; + } - /// - /// Returns the values of a as an enumerable collection. - /// - /// The type of the first element. - /// The type of the second element. - /// The type of the third element. - /// The type of the fourth element. - /// The tuple instance. - /// An enumerable collection of tuple element values. - public static IEnumerable ToValues(this ValueTuple tuple) - { - yield return tuple.Item1; - yield return tuple.Item2; - yield return tuple.Item3; - yield return tuple.Item4; - } + /// + /// Returns the values of a as an enumerable collection. + /// + /// The type of the first element. + /// The type of the second element. + /// The type of the third element. + /// The type of the fourth element. + /// The type of the fifth element. + /// The type of the sixth element. + /// The tuple instance. + /// An enumerable collection of tuple element values. + public static IEnumerable ToValues(this ValueTuple tuple) + { + yield return tuple.Item1; + yield return tuple.Item2; + yield return tuple.Item3; + yield return tuple.Item4; + yield return tuple.Item5; + yield return tuple.Item6; + } - /// - /// Returns the values of a as an enumerable collection. - /// - /// The type of the first element. - /// The type of the second element. - /// The type of the third element. - /// The type of the fourth element. - /// The type of the fifth element. - /// The tuple instance. - /// An enumerable collection of tuple element values. - public static IEnumerable ToValues(this ValueTuple tuple) - { - yield return tuple.Item1; - yield return tuple.Item2; - yield return tuple.Item3; - yield return tuple.Item4; - yield return tuple.Item5; - } + /// + /// Creates a tuple from the provided collection of values. + /// + /// An enumerable collection of values. + /// If set to true, a is created; otherwise, a is created. + /// The created tuple. + /// Thrown when is null. + public static object ToTuple(this IEnumerable values, bool isValue) + { + if (values is null) + throw new ArgumentNullException(nameof(values)); - /// - /// Returns the values of a as an enumerable collection. - /// - /// The type of the first element. - /// The type of the second element. - /// The type of the third element. - /// The type of the fourth element. - /// The type of the fifth element. - /// The type of the sixth element. - /// The tuple instance. - /// An enumerable collection of tuple element values. - public static IEnumerable ToValues(this ValueTuple tuple) - { - yield return tuple.Item1; - yield return tuple.Item2; - yield return tuple.Item3; - yield return tuple.Item4; - yield return tuple.Item5; - yield return tuple.Item6; - } + var types = new List(); + var args = new List(); - /// - /// Creates a tuple from the provided collection of values. - /// - /// An enumerable collection of values. - /// If set to true, a is created; otherwise, a is created. - /// The created tuple. - /// Thrown when is null. - public static object ToTuple(this IEnumerable values, bool isValue) + foreach (var value in values) { - if (values is null) - throw new ArgumentNullException(nameof(values)); - - var types = new List(); - var args = new List(); - - foreach (var value in values) - { - types.Add(value?.GetType() ?? typeof(object)); - args.Add(value); - } + types.Add(value?.GetType() ?? typeof(object)); + args.Add(value); + } - var prefix = isValue ? "Value" : string.Empty; + var prefix = isValue ? "Value" : string.Empty; - var genericType = $"System.{prefix}Tuple`{types.Count}".To(); - var specificType = genericType.Make(types); + var genericType = $"System.{prefix}Tuple`{types.Count}".To(); + var specificType = genericType.Make(types); - return specificType.CreateInstance([.. args]); - } + return specificType.CreateInstance([.. args]); + } - /// - /// Extracts the values of a tuple instance into an enumerable collection. - /// - /// The type of the tuple. - /// The tuple instance. - /// An enumerable collection of tuple element values. - /// Thrown when the provided object is not a tuple. - public static IEnumerable ToValues(this T tuple) - { + /// + /// Extracts the values of a tuple instance into an enumerable collection. + /// + /// The type of the tuple. + /// The tuple instance. + /// An enumerable collection of tuple element values. + /// Thrown when the provided object is not a tuple. + public static IEnumerable ToValues(this T tuple) + { #if NETSTANDARD2_0 - if (!tuple.GetType().IsTuple()) - throw new InvalidOperationException($"Type {typeof(T)} is not tuple."); + if (!tuple.GetType().IsTuple()) + throw new InvalidOperationException($"Type {typeof(T)} is not tuple."); #else - if (tuple is not ITuple) - throw new InvalidOperationException($"{tuple} is not tuple."); + if (tuple is not ITuple) + throw new InvalidOperationException($"{tuple} is not tuple."); #endif - var type = tuple.GetType(); + var type = tuple.GetType(); - if (type.IsClass) - { - return type - .GetProperties() - .Where(m => m.Name.StartsWith("Item")) - .OrderBy(m => m.Name) - .Select(m => m.GetValue(tuple)); - } - else - { - return type - .GetFields() - .Where(m => m.Name.StartsWith("Item")) - .OrderBy(m => m.Name) - .Select(m => m.GetValue(tuple)); - } + if (type.IsClass) + { + return type + .GetProperties() + .Where(m => m.Name.StartsWith("Item")) + .OrderBy(m => m.Name) + .Select(m => m.GetValue(tuple)); } - - /// - /// Recursively unwraps all inner exceptions from the provided exception. - /// - /// The exception to unwrap. - /// An enumerable collection of exceptions, including the original exception and all nested inner exceptions. - public static IEnumerable UnwrapExceptions(this Exception exception) + else { - if (exception == null) - yield break; + return type + .GetFields() + .Where(m => m.Name.StartsWith("Item")) + .OrderBy(m => m.Name) + .Select(m => m.GetValue(tuple)); + } + } + + /// + /// Recursively unwraps all inner exceptions from the provided exception. + /// + /// The exception to unwrap. + /// An enumerable collection of exceptions, including the original exception and all nested inner exceptions. + public static IEnumerable UnwrapExceptions(this Exception exception) + { + if (exception == null) + yield break; - yield return exception; + yield return exception; - if (exception is AggregateException ae) - foreach (var innerException in ae.InnerExceptions.SelectMany(ie => ie.UnwrapExceptions())) - yield return innerException; + if (exception is AggregateException ae) + foreach (var innerException in ae.InnerExceptions.SelectMany(ie => ie.UnwrapExceptions())) + yield return innerException; - if (exception.InnerException != null) - foreach (var innerException in exception.InnerException.UnwrapExceptions()) - yield return innerException; - } + if (exception.InnerException != null) + foreach (var innerException in exception.InnerException.UnwrapExceptions()) + yield return innerException; } } diff --git a/Common/TypeHelper.cs b/Common/TypeHelper.cs index ef9bf299..2d9b251f 100644 --- a/Common/TypeHelper.cs +++ b/Common/TypeHelper.cs @@ -1,489 +1,488 @@ -namespace Ecng.Common +namespace Ecng.Common; + +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.Reflection; +using System.Runtime.Serialization; +using System.Security.Cryptography; +using System.Runtime.CompilerServices; + +/// +/// Provides helper methods for working with types and related operations. +/// +public static class TypeHelper { - using System; - using System.Collections.Generic; - using System.Dynamic; - using System.Reflection; - using System.Runtime.Serialization; - using System.Security.Cryptography; - using System.Runtime.CompilerServices; + private static readonly FieldInfo _remoteStackTraceString; + + static TypeHelper() + { + // Get the _remoteStackTraceString of the Exception class + _remoteStackTraceString = typeof(Exception) + .GetField("_remoteStackTraceString", + BindingFlags.Instance | BindingFlags.NonPublic); // MS.Net + + if (_remoteStackTraceString is null) + _remoteStackTraceString = typeof(Exception) + .GetField("remote_stack_trace", + BindingFlags.Instance | BindingFlags.NonPublic); // Mono + } + + private static readonly Type _enumType = typeof(Enum); /// - /// Provides helper methods for working with types and related operations. + /// Creates an instance of the specified type using the given arguments. /// - public static class TypeHelper + /// The type to instantiate. + /// The constructor arguments. + /// Returns the created instance as an object. + public static object CreateInstance(this Type type, params object[] args) + => type.CreateInstance(args); + + /// + /// Creates an instance of a specified type T using the given arguments. + /// + /// The type to instantiate. + /// The type to instantiate. + /// The constructor arguments. + /// Returns the created instance as T. + public static T CreateInstance(this Type type, params object[] args) { - private static readonly FieldInfo _remoteStackTraceString; + if (type is null) + throw new ArgumentNullException(nameof(type)); - static TypeHelper() - { - // Get the _remoteStackTraceString of the Exception class - _remoteStackTraceString = typeof(Exception) - .GetField("_remoteStackTraceString", - BindingFlags.Instance | BindingFlags.NonPublic); // MS.Net - - if (_remoteStackTraceString is null) - _remoteStackTraceString = typeof(Exception) - .GetField("remote_stack_trace", - BindingFlags.Instance | BindingFlags.NonPublic); // Mono - } - - private static readonly Type _enumType = typeof(Enum); - - /// - /// Creates an instance of the specified type using the given arguments. - /// - /// The type to instantiate. - /// The constructor arguments. - /// Returns the created instance as an object. - public static object CreateInstance(this Type type, params object[] args) - => type.CreateInstance(args); - - /// - /// Creates an instance of a specified type T using the given arguments. - /// - /// The type to instantiate. - /// The type to instantiate. - /// The constructor arguments. - /// Returns the created instance as T. - public static T CreateInstance(this Type type, params object[] args) - { - if (type is null) - throw new ArgumentNullException(nameof(type)); - - if (args is null) - throw new ArgumentNullException(nameof(args)); - - var obj = type is ITypeConstructor ctor - ? ctor.CreateInstance(args) - : Activator.CreateInstance(type, args); - - return obj.To(); - } - - /// - /// Makes a generic type using the provided type arguments. - /// - /// The generic type definition. - /// The type arguments. - /// Returns the constructed generic type. - public static Type Make(this Type type, params Type[] args) - { - if (type is null) - throw new ArgumentNullException(nameof(type)); - - if (args is null) - throw new ArgumentNullException(nameof(args)); - - return type.MakeGenericType(args); - } - - /// - /// Makes a generic type using a list of provided type arguments. - /// - /// The generic type definition. - /// The type arguments as an IEnumerable. - /// Returns the constructed generic type. - public static Type Make(this Type type, IEnumerable args) - { - return type.Make([.. args]); - } - - /// - /// Determines whether a type is considered primitive, including common system types. - /// - /// The type to check. - /// Returns true if the type is primitive or commonly handled as a primitive. - public static bool IsPrimitive(this Type type) - { - if (type is null) - throw new ArgumentNullException(nameof(type)); - - return ( - type.IsPrimitive || - type.IsEnum() || - type == typeof(decimal) || - type == typeof(string) || - type == typeof(DateTime) || - type == typeof(DateTimeOffset) || - type == typeof(Guid) || - type == typeof(byte[]) || - type == typeof(TimeSpan) || - type == typeof(TimeZoneInfo) - ); - } - - /// - /// Determines if a type is numeric, including floating-point and decimal types. - /// - /// The type to check. - /// Returns true if the type is numeric. - public static bool IsNumeric(this Type type) - => Type.GetTypeCode(type) switch - { - TypeCode.Byte or - TypeCode.SByte or - TypeCode.UInt16 or - TypeCode.UInt32 or - TypeCode.UInt64 or - TypeCode.Int16 or - TypeCode.Int32 or - TypeCode.Int64 or - TypeCode.Decimal or - TypeCode.Double or - TypeCode.Single - => true, - _ => false, - }; - - /// - /// Determines if a type is an integer numeric type. - /// - /// The type to check. - /// Returns true if the type is an integer numeric type. - public static bool IsNumericInteger(this Type type) - => Type.GetTypeCode(type) switch - { - TypeCode.Byte or - TypeCode.SByte or - TypeCode.UInt16 or - TypeCode.UInt32 or - TypeCode.UInt64 or - TypeCode.Int16 or - TypeCode.Int32 or - TypeCode.Int64 - => true, - _ => false, - }; - - /// - /// Retrieves the type name as a string either in assembly-qualified form or not. - /// - /// The type to convert. - /// True to return the assembly-qualified name. - /// Returns the string representation of the type. - public static string GetTypeAsString(this Type type, bool isAssemblyQualifiedName) - { - if (type is null) - throw new ArgumentNullException(nameof(type)); - - return type.TryGetCSharpAlias().IsEmpty(type.GetTypeName(isAssemblyQualifiedName)); - } - - /// - /// Determines whether the specified type is a struct. - /// - /// The type to check. - /// Returns true if the type is a struct. - public static bool IsStruct(this Type type) - { - if (type is null) - throw new ArgumentNullException(nameof(type)); - - return type.IsValueType && !type.IsEnum(); - } - - /// - /// Determines whether the specified type is an enum. - /// - /// The type to check. - /// Returns true if the type is an enum. - public static bool IsEnum(this Type type) - { - if (type is null) - throw new ArgumentNullException(nameof(type)); - - // - // 2 times faster than Type.IsEnum - // - return type.BaseType == _enumType; - } - - /// - /// Determines whether the specified type is an attribute. - /// - /// The type to check. - /// Returns true if the type is an attribute. - public static bool IsAttribute(this Type type) - => type.Is(); - - /// - /// Determines whether the specified type is a delegate. - /// - /// The type to check. - /// Returns true if the type is a delegate. - public static bool IsDelegate(this Type type) - => type.Is(); - - /// - /// Creates an uninitialized instance of the specified generic type parameter. - /// - /// The type to instantiate. - /// Returns the newly created uninitialized instance. - public static TEntity CreateUnitialized() - { - return (TEntity)typeof(TEntity).CreateUnitialized(); - } - - /// - /// Creates an uninitialized instance of the specified type. - /// - /// The type to instantiate. - /// Returns the newly created uninitialized instance. - public static object CreateUnitialized(this Type type) - { - if (type is null) - throw new ArgumentNullException(nameof(type)); - - return FormatterServices.GetUninitializedObject(type); - } - - /// - /// Disposes the source if it implements IDisposable. - /// - /// The type of the source. - /// The instance to dispose. - public static void DoDispose(this TSource source) - { - if (source is IDisposable disposable) - disposable.Dispose(); - } + if (args is null) + throw new ArgumentNullException(nameof(args)); - private static readonly Lazy _applicationName = new(() => - { - var asm = Assembly.GetEntryAssembly(); - if (asm is null) - return "None"; - var attr = asm.GetAttribute(); - return attr != null ? attr.Title : asm.GetName().Name; - }); - - /// - /// Gets the name of the application. - /// - public static string ApplicationName => _applicationName.Value; - - private static readonly Lazy _applicationNameWithVersion = new(() => - { - var asm = Assembly.GetEntryAssembly(); + var obj = type is ITypeConstructor ctor + ? ctor.CreateInstance(args) + : Activator.CreateInstance(type, args); - if (asm is null) - return "None"; + return obj.To(); + } - return ApplicationName + " v" + asm.GetName().Version; - }); + /// + /// Makes a generic type using the provided type arguments. + /// + /// The generic type definition. + /// The type arguments. + /// Returns the constructed generic type. + public static Type Make(this Type type, params Type[] args) + { + if (type is null) + throw new ArgumentNullException(nameof(type)); - /// - /// Gets the name of the application with version. - /// - public static string ApplicationNameWithVersion => _applicationNameWithVersion.Value; + if (args is null) + throw new ArgumentNullException(nameof(args)); - // http://stackoverflow.com/questions/8517159/how-to-detect-at-runtime-that-net-version-4-5-currently-running-your-code + return type.MakeGenericType(args); + } - /// - /// Determines if the current environment is .NET 4.5 or newer. - /// - /// Returns true if .NET 4.5 or newer is running. - public static bool IsNet45OrNewer() - { - // Class "ReflectionContext" exists from .NET 4.5 onwards. - return Type.GetType("System.Reflection.ReflectionContext", false) != null; - } - - /// - /// Gets the fully qualified name of a type or its assembly-qualified name. - /// - /// The type to convert. - /// Whether to return the assembly-qualified name. - /// Returns the string representation of the type name. - public static string GetTypeName(this Type type, bool isAssemblyQualifiedName) + /// + /// Makes a generic type using a list of provided type arguments. + /// + /// The generic type definition. + /// The type arguments as an IEnumerable. + /// Returns the constructed generic type. + public static Type Make(this Type type, IEnumerable args) + { + return type.Make([.. args]); + } + + /// + /// Determines whether a type is considered primitive, including common system types. + /// + /// The type to check. + /// Returns true if the type is primitive or commonly handled as a primitive. + public static bool IsPrimitive(this Type type) + { + if (type is null) + throw new ArgumentNullException(nameof(type)); + + return ( + type.IsPrimitive || + type.IsEnum() || + type == typeof(decimal) || + type == typeof(string) || + type == typeof(DateTime) || + type == typeof(DateTimeOffset) || + type == typeof(Guid) || + type == typeof(byte[]) || + type == typeof(TimeSpan) || + type == typeof(TimeZoneInfo) + ); + } + + /// + /// Determines if a type is numeric, including floating-point and decimal types. + /// + /// The type to check. + /// Returns true if the type is numeric. + public static bool IsNumeric(this Type type) + => Type.GetTypeCode(type) switch { - if (type is null) - throw new ArgumentNullException(nameof(type)); - - return isAssemblyQualifiedName - ? type.AssemblyQualifiedName - : $"{type.FullName}, {type.Assembly.GetName().Name}"; - } - - /// - /// Gets the default value for the specified type. - /// - /// The type to get the default value for. - /// Returns the default value as an object. - public static object GetDefaultValue(this Type type) + TypeCode.Byte or + TypeCode.SByte or + TypeCode.UInt16 or + TypeCode.UInt32 or + TypeCode.UInt64 or + TypeCode.Int16 or + TypeCode.Int32 or + TypeCode.Int64 or + TypeCode.Decimal or + TypeCode.Double or + TypeCode.Single + => true, + _ => false, + }; + + /// + /// Determines if a type is an integer numeric type. + /// + /// The type to check. + /// Returns true if the type is an integer numeric type. + public static bool IsNumericInteger(this Type type) + => Type.GetTypeCode(type) switch { - return type.IsValueType ? Activator.CreateInstance(type) : null; - } + TypeCode.Byte or + TypeCode.SByte or + TypeCode.UInt16 or + TypeCode.UInt32 or + TypeCode.UInt64 or + TypeCode.Int16 or + TypeCode.Int32 or + TypeCode.Int64 + => true, + _ => false, + }; - // http://stackoverflow.com/questions/57383/in-c-how-can-i-rethrow-innerexception-without-losing-stack-trace + /// + /// Retrieves the type name as a string either in assembly-qualified form or not. + /// + /// The type to convert. + /// True to return the assembly-qualified name. + /// Returns the string representation of the type. + public static string GetTypeAsString(this Type type, bool isAssemblyQualifiedName) + { + if (type is null) + throw new ArgumentNullException(nameof(type)); - /// - /// Throws the specified exception while preserving the original stack trace. - /// - /// The exception to throw. - public static void Throw(this Exception ex) - { - if (ex is null) - throw new ArgumentNullException(nameof(ex)); + return type.TryGetCSharpAlias().IsEmpty(type.GetTypeName(isAssemblyQualifiedName)); + } + + /// + /// Determines whether the specified type is a struct. + /// + /// The type to check. + /// Returns true if the type is a struct. + public static bool IsStruct(this Type type) + { + if (type is null) + throw new ArgumentNullException(nameof(type)); + + return type.IsValueType && !type.IsEnum(); + } + + /// + /// Determines whether the specified type is an enum. + /// + /// The type to check. + /// Returns true if the type is an enum. + public static bool IsEnum(this Type type) + { + if (type is null) + throw new ArgumentNullException(nameof(type)); + + // + // 2 times faster than Type.IsEnum + // + return type.BaseType == _enumType; + } + + /// + /// Determines whether the specified type is an attribute. + /// + /// The type to check. + /// Returns true if the type is an attribute. + public static bool IsAttribute(this Type type) + => type.Is(); + + /// + /// Determines whether the specified type is a delegate. + /// + /// The type to check. + /// Returns true if the type is a delegate. + public static bool IsDelegate(this Type type) + => type.Is(); + + /// + /// Creates an uninitialized instance of the specified generic type parameter. + /// + /// The type to instantiate. + /// Returns the newly created uninitialized instance. + public static TEntity CreateUnitialized() + { + return (TEntity)typeof(TEntity).CreateUnitialized(); + } + + /// + /// Creates an uninitialized instance of the specified type. + /// + /// The type to instantiate. + /// Returns the newly created uninitialized instance. + public static object CreateUnitialized(this Type type) + { + if (type is null) + throw new ArgumentNullException(nameof(type)); + + return FormatterServices.GetUninitializedObject(type); + } + + /// + /// Disposes the source if it implements IDisposable. + /// + /// The type of the source. + /// The instance to dispose. + public static void DoDispose(this TSource source) + { + if (source is IDisposable disposable) + disposable.Dispose(); + } + + private static readonly Lazy _applicationName = new(() => + { + var asm = Assembly.GetEntryAssembly(); + if (asm is null) + return "None"; + var attr = asm.GetAttribute(); + return attr != null ? attr.Title : asm.GetName().Name; + }); + + /// + /// Gets the name of the application. + /// + public static string ApplicationName => _applicationName.Value; + + private static readonly Lazy _applicationNameWithVersion = new(() => + { + var asm = Assembly.GetEntryAssembly(); + + if (asm is null) + return "None"; - _remoteStackTraceString.SetValue(ex, ex.StackTrace + Environment.NewLine); + return ApplicationName + " v" + asm.GetName().Version; + }); - throw ex; - } + /// + /// Gets the name of the application with version. + /// + public static string ApplicationNameWithVersion => _applicationNameWithVersion.Value; + + // http://stackoverflow.com/questions/8517159/how-to-detect-at-runtime-that-net-version-4-5-currently-running-your-code + + /// + /// Determines if the current environment is .NET 4.5 or newer. + /// + /// Returns true if .NET 4.5 or newer is running. + public static bool IsNet45OrNewer() + { + // Class "ReflectionContext" exists from .NET 4.5 onwards. + return Type.GetType("System.Reflection.ReflectionContext", false) != null; + } + + /// + /// Gets the fully qualified name of a type or its assembly-qualified name. + /// + /// The type to convert. + /// Whether to return the assembly-qualified name. + /// Returns the string representation of the type name. + public static string GetTypeName(this Type type, bool isAssemblyQualifiedName) + { + if (type is null) + throw new ArgumentNullException(nameof(type)); + + return isAssemblyQualifiedName + ? type.AssemblyQualifiedName + : $"{type.FullName}, {type.Assembly.GetName().Name}"; + } + + /// + /// Gets the default value for the specified type. + /// + /// The type to get the default value for. + /// Returns the default value as an object. + public static object GetDefaultValue(this Type type) + { + return type.IsValueType ? Activator.CreateInstance(type) : null; + } + + // http://stackoverflow.com/questions/57383/in-c-how-can-i-rethrow-innerexception-without-losing-stack-trace + + /// + /// Throws the specified exception while preserving the original stack trace. + /// + /// The exception to throw. + public static void Throw(this Exception ex) + { + if (ex is null) + throw new ArgumentNullException(nameof(ex)); + + _remoteStackTraceString.SetValue(ex, ex.StackTrace + Environment.NewLine); + + throw ex; + } - /// - /// Generates a salt byte array of the specified size. - /// - /// The size of the salt array to create. - /// Returns a byte array representing the salt. - public static byte[] GenerateSalt(int saltSize) + /// + /// Generates a salt byte array of the specified size. + /// + /// The size of the salt array to create. + /// Returns a byte array representing the salt. + public static byte[] GenerateSalt(int saltSize) #if NET5_0_OR_GREATER - => RandomNumberGenerator.GetBytes(saltSize); + => RandomNumberGenerator.GetBytes(saltSize); #else - { - var salt = new byte[saltSize]; + { + var salt = new byte[saltSize]; - using var saltGen = new RNGCryptoServiceProvider(); - saltGen.GetBytes(salt); + using var saltGen = new RNGCryptoServiceProvider(); + saltGen.GetBytes(salt); - return salt; - } + return salt; + } #endif - /// - /// Creates a scope for the specified value with optional ownership. - /// - /// The type of the resource. - /// The resource to be scoped. - /// Whether the scope owns the instance. - /// Returns the newly created Scope. - public static Scope ToScope(this T value, bool ownInstance = true) - { - return new Scope(value, ownInstance); - } - - /// - /// Returns either a single exception or an aggregate if there are multiple exceptions. - /// - /// A collection of exceptions. - /// Returns one exception or an AggregateException. - public static Exception SingleOrAggr(this IList errors) - { - if (errors is null) - throw new ArgumentNullException(nameof(errors)); - - return errors.Count == 1 ? errors[0] : new AggregateException(errors); - } - - /// - /// Checks if a value is null and throws an exception if it is. - /// - /// The reference type to check. - /// The value to check for null. - /// The name of the parameter. - /// Returns the provided value if not null. - public static T CheckOnNull(this T value, string paramName = "value") - where T : class - { - if (value is null) - throw new ArgumentNullException(paramName); - - return value; - } - - /// - /// Retrieves the platform attribute for the specified type. - /// - /// The type to inspect. - /// Returns the platform value defined by TargetPlatformAttribute or AnyCPU. - public static Platforms GetPlatform(this Type type) => type.GetAttribute()?.Platform ?? Platforms.AnyCPU; - - /// - /// Extracts the high word from the integer. - /// - /// The integer value. - /// Returns the high word. - public static int HiWord(this int iValue) - { - return (iValue >> 16) & 0xFFFF; - } - - /// - /// Extracts the low word from the integer. - /// - /// The integer value. - /// Returns the low word. - public static int LoWord(this int iValue) - { - return iValue & 0xFFFF; - } - - // https://stackoverflow.com/a/30528667 - - /// - /// Checks if a dynamic or regular object has a property with the specified name. - /// - /// The object to inspect. - /// The property name. - /// Returns true if the property exists. - public static bool HasProperty(this object settings, string name) - { - if (settings is ExpandoObject) - return ((IDictionary)settings).ContainsKey(name); - - return settings.GetType().GetProperty(name) != null; - } - - /// - /// Indicates whether a type is the specified base type or derives from it. - /// - /// The base type to check. - /// The type to compare. - /// Allows checking if the types are the same. - /// Returns true if type is or derives from the base type. - public static bool Is(this Type type, bool canSame = true) - => type.Is(typeof(TBase), canSame); - - /// - /// Indicates whether a type is the specified base type or derives from it. - /// - /// The type to compare. - /// The base type. - /// Allows checking if the types are the same. - /// Returns true if type is or derives from the base type. - public static bool Is(this Type type, Type baseType, bool canSame = true) - => baseType.CheckOnNull(nameof(baseType)).IsAssignableFrom(type) && (canSame || type != baseType); - - /// - /// Determines if the specified type is generated by a compiler. - /// - /// The type to check. - /// Returns true if the type has a CompilerGeneratedAttribute. - public static bool IsAutoGenerated(this Type type) - => type.GetAttribute() is not null; - - /// - /// Ensures the static constructor for a type is run. - /// - /// The type to use. - public static void EnsureRunClass(this Type type) - => RuntimeHelpers.RunClassConstructor(type.TypeHandle); - - /// - /// Checks if a string is a valid web link. - /// - /// The link to check. - /// Returns true if the link is a valid absolute URI. - public static bool IsValidWebLink(this string link) - => Uri.TryCreate(link, UriKind.Absolute, out var uri) && uri.IsWebLink(); - - /// - /// Checks if a URI is a web-based link (http, https, or ftp). - /// - /// The URI to check. - /// Returns true if the URI is web-based. - public static bool IsWebLink(this Uri uri) - => uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps || uri.Scheme == Uri.UriSchemeFtp; + /// + /// Creates a scope for the specified value with optional ownership. + /// + /// The type of the resource. + /// The resource to be scoped. + /// Whether the scope owns the instance. + /// Returns the newly created Scope. + public static Scope ToScope(this T value, bool ownInstance = true) + { + return new Scope(value, ownInstance); } + + /// + /// Returns either a single exception or an aggregate if there are multiple exceptions. + /// + /// A collection of exceptions. + /// Returns one exception or an AggregateException. + public static Exception SingleOrAggr(this IList errors) + { + if (errors is null) + throw new ArgumentNullException(nameof(errors)); + + return errors.Count == 1 ? errors[0] : new AggregateException(errors); + } + + /// + /// Checks if a value is null and throws an exception if it is. + /// + /// The reference type to check. + /// The value to check for null. + /// The name of the parameter. + /// Returns the provided value if not null. + public static T CheckOnNull(this T value, string paramName = "value") + where T : class + { + if (value is null) + throw new ArgumentNullException(paramName); + + return value; + } + + /// + /// Retrieves the platform attribute for the specified type. + /// + /// The type to inspect. + /// Returns the platform value defined by TargetPlatformAttribute or AnyCPU. + public static Platforms GetPlatform(this Type type) => type.GetAttribute()?.Platform ?? Platforms.AnyCPU; + + /// + /// Extracts the high word from the integer. + /// + /// The integer value. + /// Returns the high word. + public static int HiWord(this int iValue) + { + return (iValue >> 16) & 0xFFFF; + } + + /// + /// Extracts the low word from the integer. + /// + /// The integer value. + /// Returns the low word. + public static int LoWord(this int iValue) + { + return iValue & 0xFFFF; + } + + // https://stackoverflow.com/a/30528667 + + /// + /// Checks if a dynamic or regular object has a property with the specified name. + /// + /// The object to inspect. + /// The property name. + /// Returns true if the property exists. + public static bool HasProperty(this object settings, string name) + { + if (settings is ExpandoObject) + return ((IDictionary)settings).ContainsKey(name); + + return settings.GetType().GetProperty(name) != null; + } + + /// + /// Indicates whether a type is the specified base type or derives from it. + /// + /// The base type to check. + /// The type to compare. + /// Allows checking if the types are the same. + /// Returns true if type is or derives from the base type. + public static bool Is(this Type type, bool canSame = true) + => type.Is(typeof(TBase), canSame); + + /// + /// Indicates whether a type is the specified base type or derives from it. + /// + /// The type to compare. + /// The base type. + /// Allows checking if the types are the same. + /// Returns true if type is or derives from the base type. + public static bool Is(this Type type, Type baseType, bool canSame = true) + => baseType.CheckOnNull(nameof(baseType)).IsAssignableFrom(type) && (canSame || type != baseType); + + /// + /// Determines if the specified type is generated by a compiler. + /// + /// The type to check. + /// Returns true if the type has a CompilerGeneratedAttribute. + public static bool IsAutoGenerated(this Type type) + => type.GetAttribute() is not null; + + /// + /// Ensures the static constructor for a type is run. + /// + /// The type to use. + public static void EnsureRunClass(this Type type) + => RuntimeHelpers.RunClassConstructor(type.TypeHandle); + + /// + /// Checks if a string is a valid web link. + /// + /// The link to check. + /// Returns true if the link is a valid absolute URI. + public static bool IsValidWebLink(this string link) + => Uri.TryCreate(link, UriKind.Absolute, out var uri) && uri.IsWebLink(); + + /// + /// Checks if a URI is a web-based link (http, https, or ftp). + /// + /// The URI to check. + /// Returns true if the URI is web-based. + public static bool IsWebLink(this Uri uri) + => uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps || uri.Scheme == Uri.UriSchemeFtp; } \ No newline at end of file diff --git a/Common/Watch.cs b/Common/Watch.cs index d9b7e808..0a5ad708 100644 --- a/Common/Watch.cs +++ b/Common/Watch.cs @@ -1,26 +1,25 @@ -namespace Ecng.Common -{ - using System; - using System.Diagnostics; +namespace Ecng.Common; + +using System; +using System.Diagnostics; +/// +/// Provides utility methods for timing the execution of code. +/// +public static class Watch +{ /// - /// Provides utility methods for timing the execution of code. + /// Executes the specified action, measures its execution time, and returns the elapsed time. /// - public static class Watch + /// The action to execute. + /// + /// A representing the elapsed time during the execution of the action. + /// + public static TimeSpan Do(Action action) { - /// - /// Executes the specified action, measures its execution time, and returns the elapsed time. - /// - /// The action to execute. - /// - /// A representing the elapsed time during the execution of the action. - /// - public static TimeSpan Do(Action action) - { - var watch = Stopwatch.StartNew(); - action(); - watch.Stop(); - return watch.Elapsed; - } + var watch = Stopwatch.StartNew(); + action(); + watch.Stop(); + return watch.Elapsed; } } \ No newline at end of file diff --git a/Common/Wrapper.cs b/Common/Wrapper.cs index e0b541a4..deb38d0a 100644 --- a/Common/Wrapper.cs +++ b/Common/Wrapper.cs @@ -1,165 +1,164 @@ -namespace Ecng.Common -{ - #region Using Directives +namespace Ecng.Common; - using System; - using System.Collections; - using System.Collections.Generic; - using System.Linq; +#region Using Directives - #endregion +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +#endregion + +/// +/// Represents an abstract wrapper class for a value of type that supports equality, cloning, and disposal. +/// +/// The type of the wrapped value. +[Serializable] +public abstract class Wrapper : Equatable>, IDisposable +{ + #region Wrapper.ctor() /// - /// Represents an abstract wrapper class for a value of type that supports equality, cloning, and disposal. + /// Initializes a new instance of the class. /// - /// The type of the wrapped value. - [Serializable] - public abstract class Wrapper : Equatable>, IDisposable + protected Wrapper() { - #region Wrapper.ctor() + } - /// - /// Initializes a new instance of the class. - /// - protected Wrapper() - { - } + /// + /// Initializes a new instance of the class with the specified value. + /// + /// The value to wrap. + protected Wrapper(T value) + { + Value = value; + } - /// - /// Initializes a new instance of the class with the specified value. - /// - /// The value to wrap. - protected Wrapper(T value) - { - Value = value; - } + #endregion - #endregion + #region Properties - #region Properties + /// + /// Gets or sets the wrapped value. + /// + public virtual T Value { get; set; } + + /// + /// Gets a value indicating whether the wrapped value is not equal to the default value of type . + /// + public bool HasValue => !ReferenceEquals(Value, default(T)); - /// - /// Gets or sets the wrapped value. - /// - public virtual T Value { get; set; } + #endregion - /// - /// Gets a value indicating whether the wrapped value is not equal to the default value of type . - /// - public bool HasValue => !ReferenceEquals(Value, default(T)); + #region Operators - #endregion + /// + /// Defines an explicit conversion operator from to . + /// + /// The wrapper instance. + /// The wrapped value. + public static explicit operator T(Wrapper wrapper) + { + return wrapper.Value; + } - #region Operators + #endregion - /// - /// Defines an explicit conversion operator from to . - /// - /// The wrapper instance. - /// The wrapped value. - public static explicit operator T(Wrapper wrapper) - { - return wrapper.Value; - } + #region Equality Members - #endregion + /// + /// Determines whether the wrapped value of this instance equals the wrapped value of another instance. + /// + /// Another instance of to compare with. + /// true if the wrapped values are equal; otherwise, false. + protected override bool OnEquals(Wrapper other) + { + if (Value is IEnumerable) + return ((IEnumerable)Value).SequenceEqual((IEnumerable)other.Value); + else + return Value.Equals(other.Value); + } - #region Equality Members + /// + /// Returns a hash code for this instance based on the wrapped value. + /// + /// A hash code for the current object. + public override int GetHashCode() + { + int hash = 0; - /// - /// Determines whether the wrapped value of this instance equals the wrapped value of another instance. - /// - /// Another instance of to compare with. - /// true if the wrapped values are equal; otherwise, false. - protected override bool OnEquals(Wrapper other) + if (HasValue) { - if (Value is IEnumerable) - return ((IEnumerable)Value).SequenceEqual((IEnumerable)other.Value); + if (Value is ICollection) + throw new NotImplementedException(); else - return Value.Equals(other.Value); + hash = Value.GetHashCode(); } - /// - /// Returns a hash code for this instance based on the wrapped value. - /// - /// A hash code for the current object. - public override int GetHashCode() - { - int hash = 0; - - if (HasValue) - { - if (Value is ICollection) - throw new NotImplementedException(); - else - hash = Value.GetHashCode(); - } - - return hash; - } + return hash; + } - #endregion + #endregion - #region IDisposable Members + #region IDisposable Members - /// - /// Gets a value indicating whether this instance has already been disposed. - /// - public bool IsDisposed { get; private set; } + /// + /// Gets a value indicating whether this instance has already been disposed. + /// + public bool IsDisposed { get; private set; } - /// - /// Releases all resources used by the current instance. - /// - public void Dispose() + /// + /// Releases all resources used by the current instance. + /// + public void Dispose() + { + lock (this) { - lock (this) + if (!IsDisposed) { - if (!IsDisposed) - { - DisposeManaged(); - DisposeNative(); - IsDisposed = true; - GC.SuppressFinalize(this); - } + DisposeManaged(); + DisposeNative(); + IsDisposed = true; + GC.SuppressFinalize(this); } } + } - /// - /// Releases managed resources used by the current instance. - /// - protected virtual void DisposeManaged() - { - Value.DoDispose(); - } - - /// - /// Releases unmanaged (native) resources used by the current instance. - /// - protected virtual void DisposeNative() - { - } + /// + /// Releases managed resources used by the current instance. + /// + protected virtual void DisposeManaged() + { + Value.DoDispose(); + } - /// - /// Finalizes an instance of the class. - /// - ~Wrapper() - { - DisposeNative(); - } + /// + /// Releases unmanaged (native) resources used by the current instance. + /// + protected virtual void DisposeNative() + { + } - #endregion + /// + /// Finalizes an instance of the class. + /// + ~Wrapper() + { + DisposeNative(); + } - #region Object Overrides + #endregion - /// - /// Returns a string that represents the current instance. - /// - /// A string representation of the wrapped value, or an empty string if no value is present. - public override string ToString() - { - return HasValue ? Value.To() : string.Empty; - } + #region Object Overrides - #endregion + /// + /// Returns a string that represents the current instance. + /// + /// A string representation of the wrapped value, or an empty string if no value is present. + public override string ToString() + { + return HasValue ? Value.To() : string.Empty; } + + #endregion } \ No newline at end of file diff --git a/Common/XmlHelper.cs b/Common/XmlHelper.cs index e77f8199..68f108ce 100644 --- a/Common/XmlHelper.cs +++ b/Common/XmlHelper.cs @@ -1,103 +1,102 @@ -namespace Ecng.Common -{ - using System; - using System.Linq; - using System.Xml; - using System.Xml.Linq; +namespace Ecng.Common; + +using System; +using System.Linq; +using System.Xml; +using System.Xml.Linq; +/// +/// Provides helper extension methods for working with XML elements, attributes, and XML strings. +/// +public static class XmlHelper +{ /// - /// Provides helper extension methods for working with XML elements, attributes, and XML strings. + /// Retrieves the value of the child element with the specified name and converts it to the specified type. /// - public static class XmlHelper + /// The type to which the element's value should be converted. + /// The parent XML element. + /// The name of the child element. + /// The default value to return if the element is not found. + /// The converted value of the child element, or the default value if the element does not exist. + /// Thrown when the parent element is null. + public static T GetElementValue(this XElement parent, XName name, T defaultValue = default) { - /// - /// Retrieves the value of the child element with the specified name and converts it to the specified type. - /// - /// The type to which the element's value should be converted. - /// The parent XML element. - /// The name of the child element. - /// The default value to return if the element is not found. - /// The converted value of the child element, or the default value if the element does not exist. - /// Thrown when the parent element is null. - public static T GetElementValue(this XElement parent, XName name, T defaultValue = default) - { - if (parent is null) - throw new ArgumentNullException(nameof(parent)); + if (parent is null) + throw new ArgumentNullException(nameof(parent)); - var elem = parent.Element(name); - return elem is null ? defaultValue : elem.Value.To(); - } + var elem = parent.Element(name); + return elem is null ? defaultValue : elem.Value.To(); + } - /// - /// Retrieves the value of the attribute with the specified name and converts it to the specified type. - /// - /// The type to which the attribute's value should be converted. - /// The XML element containing the attribute. - /// The name of the attribute. - /// The default value to return if the attribute is not found. - /// The converted value of the attribute, or the default value if the attribute does not exist. - /// Thrown when the XML element is null. - public static T GetAttributeValue(this XElement elem, XName name, T defaultValue = default) - { - if (elem is null) - throw new ArgumentNullException(nameof(elem)); + /// + /// Retrieves the value of the attribute with the specified name and converts it to the specified type. + /// + /// The type to which the attribute's value should be converted. + /// The XML element containing the attribute. + /// The name of the attribute. + /// The default value to return if the attribute is not found. + /// The converted value of the attribute, or the default value if the attribute does not exist. + /// Thrown when the XML element is null. + public static T GetAttributeValue(this XElement elem, XName name, T defaultValue = default) + { + if (elem is null) + throw new ArgumentNullException(nameof(elem)); - var attr = elem.Attribute(name); - return attr is null ? defaultValue : attr.Value.To(); - } + var attr = elem.Attribute(name); + return attr is null ? defaultValue : attr.Value.To(); + } - /// - /// Writes an attribute with the specified name and value to the XmlWriter. - /// - /// The XML writer. - /// The name of the attribute to write. - /// The value of the attribute. If null, an empty string is written. - /// The same instance of the XmlWriter to allow for method chaining. - /// Thrown when the XmlWriter is null. - public static XmlWriter WriteAttribute(this XmlWriter writer, string name, object value) - { - if (writer is null) - throw new ArgumentNullException(nameof(writer)); + /// + /// Writes an attribute with the specified name and value to the XmlWriter. + /// + /// The XML writer. + /// The name of the attribute to write. + /// The value of the attribute. If null, an empty string is written. + /// The same instance of the XmlWriter to allow for method chaining. + /// Thrown when the XmlWriter is null. + public static XmlWriter WriteAttribute(this XmlWriter writer, string name, object value) + { + if (writer is null) + throw new ArgumentNullException(nameof(writer)); - writer.WriteAttributeString(name, value != null ? value.To() : string.Empty); + writer.WriteAttributeString(name, value != null ? value.To() : string.Empty); - return writer; - } + return writer; + } - /// - /// Compares two XmlNode objects based on their OuterXml representation. - /// - /// The first XmlNode to compare. - /// The second XmlNode to compare. - /// True if both XmlNodes have identical OuterXml representations; otherwise, false. - /// - /// Thrown when the first or second XmlNode is null. - /// - public static bool Compare(this XmlNode first, XmlNode second) - { - if (first is null) - throw new ArgumentNullException(nameof(first)); + /// + /// Compares two XmlNode objects based on their OuterXml representation. + /// + /// The first XmlNode to compare. + /// The second XmlNode to compare. + /// True if both XmlNodes have identical OuterXml representations; otherwise, false. + /// + /// Thrown when the first or second XmlNode is null. + /// + public static bool Compare(this XmlNode first, XmlNode second) + { + if (first is null) + throw new ArgumentNullException(nameof(first)); - if (second is null) - throw new ArgumentNullException(nameof(second)); + if (second is null) + throw new ArgumentNullException(nameof(second)); - return first.OuterXml == second.OuterXml; - } + return first.OuterXml == second.OuterXml; + } - /// - /// Determines whether the specified string contains only valid XML characters. - /// - /// The string to check. - /// True if all characters in the string are valid XML characters; otherwise, false. - public static bool IsXmlString(this string value) - => value.All(IsXmlChar); + /// + /// Determines whether the specified string contains only valid XML characters. + /// + /// The string to check. + /// True if all characters in the string are valid XML characters; otherwise, false. + public static bool IsXmlString(this string value) + => value.All(IsXmlChar); - /// - /// Determines whether the specified character is a valid XML character. - /// - /// The character to check. - /// True if the character is a valid XML character; otherwise, false. - public static bool IsXmlChar(this char c) - => XmlConvert.IsXmlChar(c); - } + /// + /// Determines whether the specified character is a valid XML character. + /// + /// The character to check. + /// True if the character is a valid XML character; otherwise, false. + public static bool IsXmlChar(this char c) + => XmlConvert.IsXmlChar(c); } \ No newline at end of file diff --git a/Compilation.Roslyn/RoslynCompiler.cs b/Compilation.Roslyn/RoslynCompiler.cs index db934c2a..b0e1ec84 100644 --- a/Compilation.Roslyn/RoslynCompiler.cs +++ b/Compilation.Roslyn/RoslynCompiler.cs @@ -1,227 +1,226 @@ -namespace Ecng.Compilation.Roslyn +namespace Ecng.Compilation.Roslyn; + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; + +using Ecng.Common; +using Ecng.Compilation; +using Ecng.Localization; + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.VisualBasic; + +/// +/// Represents a Roslyn compiler. +/// +/// The file extension of the source files that the compiler supports. +public abstract class RoslynCompiler(string extension) : ICompiler { - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Reflection; - using System.Threading; - using System.Threading.Tasks; - - using Ecng.Common; - using Ecng.Compilation; - using Ecng.Localization; - - using Microsoft.CodeAnalysis; - using Microsoft.CodeAnalysis.CSharp; - using Microsoft.CodeAnalysis.Diagnostics; - using Microsoft.CodeAnalysis.VisualBasic; - - /// - /// Represents a Roslyn compiler. - /// - /// The file extension of the source files that the compiler supports. - public abstract class RoslynCompiler(string extension) : ICompiler + private static readonly Dictionary _redirects = new() { - private static readonly Dictionary _redirects = new() - { - { "System.Collections.Immutable, Version=1.2.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", "System.Collections.Immutable.dll"}, - { "System.IO.FileSystem, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", "System.IO.FileSystem.dll"}, - //{ "System.IO.FileSystem, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", "System.IO.FileSystem.dll"}, - //{ "System.Security.Cryptography.Primitives, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", "System.Security.Cryptography.Primitives.dll"}, - }; + { "System.Collections.Immutable, Version=1.2.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", "System.Collections.Immutable.dll"}, + { "System.IO.FileSystem, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", "System.IO.FileSystem.dll"}, + //{ "System.IO.FileSystem, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", "System.IO.FileSystem.dll"}, + //{ "System.Security.Cryptography.Primitives, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", "System.Security.Cryptography.Primitives.dll"}, + }; - static RoslynCompiler() + static RoslynCompiler() + { + AppDomain.CurrentDomain.AssemblyResolve += (_, args) => { - AppDomain.CurrentDomain.AssemblyResolve += (_, args) => + if (_redirects.ContainsKey(args.Name)) { - if (_redirects.ContainsKey(args.Name)) - { - var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, _redirects[args.Name]); - return Assembly.LoadFrom(path); - } + var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, _redirects[args.Name]); + return Assembly.LoadFrom(path); + } - return null; - }; - } + return null; + }; + } - bool ICompiler.IsAssemblyPersistable { get; } = true; - string ICompiler.Extension { get; } = extension; + bool ICompiler.IsAssemblyPersistable { get; } = true; + string ICompiler.Extension { get; } = extension; - /// - public abstract bool IsTabsSupported { get; } - /// - public abstract bool IsCaseSensitive { get; } - /// - public abstract bool IsReferencesSupported { get; } + /// + public abstract bool IsTabsSupported { get; } + /// + public abstract bool IsCaseSensitive { get; } + /// + public abstract bool IsReferencesSupported { get; } - private Compilation Create(string name, IEnumerable sources, IEnumerable<(string name, byte[] body)> refs, CancellationToken cancellationToken) - { - if (sources is null) - throw new ArgumentNullException(nameof(sources)); + private Compilation Create(string name, IEnumerable sources, IEnumerable<(string name, byte[] body)> refs, CancellationToken cancellationToken) + { + if (sources is null) + throw new ArgumentNullException(nameof(sources)); - if (refs is null) - throw new ArgumentNullException(nameof(refs)); + if (refs is null) + throw new ArgumentNullException(nameof(refs)); - var assemblyName = name + Path.GetRandomFileName(); + var assemblyName = name + Path.GetRandomFileName(); - var references = refs.Select(r => MetadataReference.CreateFromImage(r.body)).ToArray(); + var references = refs.Select(r => MetadataReference.CreateFromImage(r.body)).ToArray(); - return Create(assemblyName, sources, references, cancellationToken); - } + return Create(assemblyName, sources, references, cancellationToken); + } - /// - /// Creates a new compilation instance. - /// - /// The name of the assembly. - /// The source code files as strings. - /// A collection of references. - /// - /// - protected abstract Compilation Create( - string assemblyName, - IEnumerable sources, - PortableExecutableReference[] references, - CancellationToken cancellationToken); - - async Task ICompiler.Analyse(object analyzer, IEnumerable analyzerSettings, string name, IEnumerable sources, IEnumerable<(string name, byte[] body)> refs, CancellationToken cancellationToken) - { - if (analyzer is null) - throw new ArgumentNullException(nameof(analyzer)); + /// + /// Creates a new compilation instance. + /// + /// The name of the assembly. + /// The source code files as strings. + /// A collection of references. + /// + /// + protected abstract Compilation Create( + string assemblyName, + IEnumerable sources, + PortableExecutableReference[] references, + CancellationToken cancellationToken); + + async Task ICompiler.Analyse(object analyzer, IEnumerable analyzerSettings, string name, IEnumerable sources, IEnumerable<(string name, byte[] body)> refs, CancellationToken cancellationToken) + { + if (analyzer is null) + throw new ArgumentNullException(nameof(analyzer)); - if (analyzerSettings is null) - throw new ArgumentNullException(nameof(analyzerSettings)); + if (analyzerSettings is null) + throw new ArgumentNullException(nameof(analyzerSettings)); - var compilation = Create(name, sources, refs, cancellationToken); + var compilation = Create(name, sources, refs, cancellationToken); - var compilationWithAnalyzers = compilation.WithAnalyzers( - [(DiagnosticAnalyzer)analyzer], - new AnalyzerOptions([.. analyzerSettings.Cast()])); + var compilationWithAnalyzers = compilation.WithAnalyzers( + [(DiagnosticAnalyzer)analyzer], + new AnalyzerOptions([.. analyzerSettings.Cast()])); - static CompilationErrorTypes ToType(DiagnosticSeverity severity) - => severity switch - { - DiagnosticSeverity.Info => CompilationErrorTypes.Info, - DiagnosticSeverity.Warning => CompilationErrorTypes.Warning, - DiagnosticSeverity.Error => CompilationErrorTypes.Error, - _ => throw new ArgumentOutOfRangeException(severity.To()), - }; + static CompilationErrorTypes ToType(DiagnosticSeverity severity) + => severity switch + { + DiagnosticSeverity.Info => CompilationErrorTypes.Info, + DiagnosticSeverity.Warning => CompilationErrorTypes.Warning, + DiagnosticSeverity.Error => CompilationErrorTypes.Error, + _ => throw new ArgumentOutOfRangeException(severity.To()), + }; + + var analyzerDiagnostics = await compilationWithAnalyzers.GetAllDiagnosticsAsync(cancellationToken); + return [.. analyzerDiagnostics.Select(e => + { + var lineSpan = e.Location.GetLineSpan(); - var analyzerDiagnostics = await compilationWithAnalyzers.GetAllDiagnosticsAsync(cancellationToken); - return [.. analyzerDiagnostics.Select(e => + return new CompilationError { - var lineSpan = e.Location.GetLineSpan(); + Type = ToType(e.Severity), + Message = e.GetMessage(), + Line = lineSpan.StartLinePosition.Line, + Character = lineSpan.StartLinePosition.Character, + Id = e.Id, + }; + }).Distinct()]; + } - return new CompilationError - { - Type = ToType(e.Severity), - Message = e.GetMessage(), - Line = lineSpan.StartLinePosition.Line, - Character = lineSpan.StartLinePosition.Character, - Id = e.Id, - }; - }).Distinct()]; - } + Task ICompiler.Compile(string name, IEnumerable sources, IEnumerable<(string name, byte[] body)> refs, CancellationToken cancellationToken) + { + var compilation = Create(name, sources, refs, cancellationToken); - Task ICompiler.Compile(string name, IEnumerable sources, IEnumerable<(string name, byte[] body)> refs, CancellationToken cancellationToken) - { - var compilation = Create(name, sources, refs, cancellationToken); + using var ms = new MemoryStream(); - using var ms = new MemoryStream(); + byte[] getBody() + { + ms.Seek(0, SeekOrigin.Begin); + return ms.To(); + } - byte[] getBody() - { - ms.Seek(0, SeekOrigin.Begin); - return ms.To(); - } + var result = compilation.Emit(ms, cancellationToken: cancellationToken); - var result = compilation.Emit(ms, cancellationToken: cancellationToken); + var compilationResult = new AssemblyCompilationResult(result.Diagnostics.Select(diagnostic => + { + var pos = diagnostic.Location.GetLineSpan().StartLinePosition; - var compilationResult = new AssemblyCompilationResult(result.Diagnostics.Select(diagnostic => + var error = new CompilationError { - var pos = diagnostic.Location.GetLineSpan().StartLinePosition; - - var error = new CompilationError + Id = diagnostic.Id, + Line = pos.Line, + Character = pos.Character, + Message = diagnostic.GetMessage(), + Type = diagnostic.Severity switch { - Id = diagnostic.Id, - Line = pos.Line, - Character = pos.Character, - Message = diagnostic.GetMessage(), - Type = diagnostic.Severity switch - { - DiagnosticSeverity.Hidden or DiagnosticSeverity.Info => CompilationErrorTypes.Info, - DiagnosticSeverity.Warning => CompilationErrorTypes.Warning, - DiagnosticSeverity.Error => CompilationErrorTypes.Error, - _ => throw new ArgumentOutOfRangeException(nameof(diagnostic), diagnostic.Severity, "Invalid value.".Localize()), - } - }; - - return error; - }), result.Success ? getBody() : null); - - return ((CompilationResult)compilationResult).FromResult(); - } + DiagnosticSeverity.Hidden or DiagnosticSeverity.Info => CompilationErrorTypes.Info, + DiagnosticSeverity.Warning => CompilationErrorTypes.Warning, + DiagnosticSeverity.Error => CompilationErrorTypes.Error, + _ => throw new ArgumentOutOfRangeException(nameof(diagnostic), diagnostic.Severity, "Invalid value.".Localize()), + } + }; + + return error; + }), result.Success ? getBody() : null); - ICompilerContext ICompiler.CreateContext() => new AssemblyLoadContextTracker(); + return ((CompilationResult)compilationResult).FromResult(); } + ICompilerContext ICompiler.CreateContext() => new AssemblyLoadContextTracker(); +} + +/// +/// Represents a C# compiler that supports compiling source files into an assembly. +/// +public class CSharpCompiler : RoslynCompiler +{ /// - /// Represents a C# compiler that supports compiling source files into an assembly. + /// Initializes a new instance of the class. /// - public class CSharpCompiler : RoslynCompiler + public CSharpCompiler() + : base(FileExts.CSharp) { - /// - /// Initializes a new instance of the class. - /// - public CSharpCompiler() - : base(FileExts.CSharp) - { - } - - /// - public override bool IsTabsSupported => true; - /// - public override bool IsCaseSensitive => true; - /// - public override bool IsReferencesSupported => true; - - /// - protected override Compilation Create(string assemblyName, IEnumerable sources, PortableExecutableReference[] references, CancellationToken cancellationToken) - => CSharpCompilation.Create( - assemblyName, - sources.Select(source => CSharpSyntaxTree.ParseText(source, cancellationToken: cancellationToken)), - references, - new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) - ); } + /// + public override bool IsTabsSupported => true; + /// + public override bool IsCaseSensitive => true; + /// + public override bool IsReferencesSupported => true; + + /// + protected override Compilation Create(string assemblyName, IEnumerable sources, PortableExecutableReference[] references, CancellationToken cancellationToken) + => CSharpCompilation.Create( + assemblyName, + sources.Select(source => CSharpSyntaxTree.ParseText(source, cancellationToken: cancellationToken)), + references, + new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) + ); +} + +/// +/// Represents a Visual Basic compiler that supports compiling source files into an assembly. +/// +public class VisualBasicCompiler : RoslynCompiler +{ /// - /// Represents a Visual Basic compiler that supports compiling source files into an assembly. + /// Initializes a new instance of the class. /// - public class VisualBasicCompiler : RoslynCompiler + public VisualBasicCompiler() + : base(FileExts.VisualBasic) { - /// - /// Initializes a new instance of the class. - /// - public VisualBasicCompiler() - : base(FileExts.VisualBasic) - { - } - - /// - public override bool IsTabsSupported => true; - /// - public override bool IsCaseSensitive => false; - /// - public override bool IsReferencesSupported => true; - - /// - protected override Compilation Create(string assemblyName, IEnumerable sources, PortableExecutableReference[] references, CancellationToken cancellationToken) - => VisualBasicCompilation.Create( - assemblyName, - sources.Select(source => VisualBasicSyntaxTree.ParseText(source, cancellationToken: cancellationToken)), - references, - new VisualBasicCompilationOptions(OutputKind.DynamicallyLinkedLibrary) - ); } + + /// + public override bool IsTabsSupported => true; + /// + public override bool IsCaseSensitive => false; + /// + public override bool IsReferencesSupported => true; + + /// + protected override Compilation Create(string assemblyName, IEnumerable sources, PortableExecutableReference[] references, CancellationToken cancellationToken) + => VisualBasicCompilation.Create( + assemblyName, + sources.Select(source => VisualBasicSyntaxTree.ParseText(source, cancellationToken: cancellationToken)), + references, + new VisualBasicCompilationOptions(OutputKind.DynamicallyLinkedLibrary) + ); } \ No newline at end of file diff --git a/Compilation/CompilationErrorTypes.cs b/Compilation/CompilationErrorTypes.cs index cf2a9395..ad548cfe 100644 --- a/Compilation/CompilationErrorTypes.cs +++ b/Compilation/CompilationErrorTypes.cs @@ -1,23 +1,22 @@ -namespace Ecng.Compilation +namespace Ecng.Compilation; + +/// +/// Represents the type of a compilation error. +/// +public enum CompilationErrorTypes { /// - /// Represents the type of a compilation error. + /// Represents an informational message. /// - public enum CompilationErrorTypes - { - /// - /// Represents an informational message. - /// - Info, + Info, - /// - /// Represents a warning message. - /// - Warning, + /// + /// Represents a warning message. + /// + Warning, - /// - /// Represents an error message. - /// - Error, - } + /// + /// Represents an error message. + /// + Error, } \ No newline at end of file diff --git a/Compilation/CompilationResult.cs b/Compilation/CompilationResult.cs index 42d5249d..1311e3c2 100644 --- a/Compilation/CompilationResult.cs +++ b/Compilation/CompilationResult.cs @@ -2,7 +2,6 @@ namespace Ecng.Compilation; using System; using System.Collections.Generic; -using System.Linq; using System.Reflection; /// diff --git a/Compilation/ICompiler.cs b/Compilation/ICompiler.cs index d129d5d1..01895489 100644 --- a/Compilation/ICompiler.cs +++ b/Compilation/ICompiler.cs @@ -1,65 +1,64 @@ -namespace Ecng.Compilation -{ - using System.Collections.Generic; - using System.Threading; - using System.Threading.Tasks; +namespace Ecng.Compilation; + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +/// +/// Defines the contract for a compiler that supports compiling source files into an assembly. +/// +public interface ICompiler +{ /// - /// Defines the contract for a compiler that supports compiling source files into an assembly. + /// Gets a value indicating whether the compiled assembly can be persisted. /// - public interface ICompiler - { - /// - /// Gets a value indicating whether the compiled assembly can be persisted. - /// - bool IsAssemblyPersistable { get; } + bool IsAssemblyPersistable { get; } - /// - /// Gets the file extension used by the compiler. - /// - string Extension { get; } + /// + /// Gets the file extension used by the compiler. + /// + string Extension { get; } - /// - /// Gets a value indicating whether the compiler supports tabs in the source code. - /// - bool IsTabsSupported { get; } + /// + /// Gets a value indicating whether the compiler supports tabs in the source code. + /// + bool IsTabsSupported { get; } - /// - /// Gets a value indicating whether the language is case sensitive. - /// - bool IsCaseSensitive { get; } + /// + /// Gets a value indicating whether the language is case sensitive. + /// + bool IsCaseSensitive { get; } - /// - /// Gets a value indicating whether the compiler supports external references. - /// - bool IsReferencesSupported { get; } + /// + /// Gets a value indicating whether the compiler supports external references. + /// + bool IsReferencesSupported { get; } - /// - /// Creates a new compiler context. - /// - /// A new instance of . - ICompilerContext CreateContext(); + /// + /// Creates a new compiler context. + /// + /// A new instance of . + ICompilerContext CreateContext(); - /// - /// Analyzes the provided source code using the specified analyzer and settings. - /// - /// The analyzer used to perform the analysis. - /// The settings applied to the analyzer. - /// The name of the compilation unit. - /// The source code files as strings. - /// A collection of references as tuples containing the name and binary content. - /// A token to monitor for cancellation requests. - /// A task that represents the asynchronous analysis operation. The task result contains an array of instances. - Task Analyse(object analyzer, IEnumerable analyzerSettings, string name, IEnumerable sources, IEnumerable<(string name, byte[] body)> refs, CancellationToken cancellationToken = default); + /// + /// Analyzes the provided source code using the specified analyzer and settings. + /// + /// The analyzer used to perform the analysis. + /// The settings applied to the analyzer. + /// The name of the compilation unit. + /// The source code files as strings. + /// A collection of references as tuples containing the name and binary content. + /// A token to monitor for cancellation requests. + /// A task that represents the asynchronous analysis operation. The task result contains an array of instances. + Task Analyse(object analyzer, IEnumerable analyzerSettings, string name, IEnumerable sources, IEnumerable<(string name, byte[] body)> refs, CancellationToken cancellationToken = default); - /// - /// Compiles the provided source code files with the specified references. - /// - /// The name of the compilation unit. - /// The source code files as strings. - /// A collection of references as tuples containing the name and binary content. - /// A token to monitor for cancellation requests. - /// A task that represents the asynchronous compile operation. The task result contains a . - Task Compile(string name, IEnumerable sources, IEnumerable<(string name, byte[] body)> refs, CancellationToken cancellationToken = default); - } + /// + /// Compiles the provided source code files with the specified references. + /// + /// The name of the compilation unit. + /// The source code files as strings. + /// A collection of references as tuples containing the name and binary content. + /// A token to monitor for cancellation requests. + /// A task that represents the asynchronous compile operation. The task result contains a . + Task Compile(string name, IEnumerable sources, IEnumerable<(string name, byte[] body)> refs, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/ComponentModel/BaseObservableCollection.cs b/ComponentModel/BaseObservableCollection.cs index ab83e14e..012611ae 100644 --- a/ComponentModel/BaseObservableCollection.cs +++ b/ComponentModel/BaseObservableCollection.cs @@ -1,47 +1,46 @@ -namespace Ecng.ComponentModel +namespace Ecng.ComponentModel; + +using System; + +/// +/// Base class for custom observable collections. +/// +public abstract class BaseObservableCollection { - using System; + private int _maxCount = -1; /// - /// Base class for custom observable collections. + /// Max number of elements before collection will auto trim itself. -1 to disable. /// - public abstract class BaseObservableCollection + public int MaxCount { - private int _maxCount = -1; - - /// - /// Max number of elements before collection will auto trim itself. -1 to disable. - /// - public int MaxCount + get => _maxCount; + set { - get => _maxCount; - set - { - if (value < -1 || value == 0) - throw new ArgumentOutOfRangeException(); - - _maxCount = value; - } + if (value < -1 || value == 0) + throw new ArgumentOutOfRangeException(); + + _maxCount = value; } + } - /// - /// - public abstract int Count { get; } + /// + /// + public abstract int Count { get; } - /// - /// - public abstract int RemoveRange(int index, int count); + /// + /// + public abstract int RemoveRange(int index, int count); - /// - /// Check current count and trim if necessary. - /// - protected void CheckCount() - { - if (MaxCount == -1) - return; + /// + /// Check current count and trim if necessary. + /// + protected void CheckCount() + { + if (MaxCount == -1) + return; - if(Count > 1.5 * MaxCount) - RemoveRange(0, Count - MaxCount); - } + if(Count > 1.5 * MaxCount) + RemoveRange(0, Count - MaxCount); } } \ No newline at end of file diff --git a/ComponentModel/ComparisonOperatorExtensions.cs b/ComponentModel/ComparisonOperatorExtensions.cs index 9ce7d7c9..697912e0 100644 --- a/ComponentModel/ComparisonOperatorExtensions.cs +++ b/ComponentModel/ComparisonOperatorExtensions.cs @@ -1,70 +1,69 @@ -namespace Ecng.ComponentModel -{ - using System; +namespace Ecng.ComponentModel; + +using System; - using Ecng.Common; +using Ecng.Common; +/// +/// Provides extension methods for comparing string values using a specified comparison operator. +/// +public static class LikeComparesExtensions +{ /// - /// Provides extension methods for comparing string values using a specified comparison operator. + /// Determines if the specified string value matches a pattern based on the provided comparison operator. /// - public static class LikeComparesExtensions + /// The string value to evaluate. + /// The pattern to compare against. + /// The comparison operator to use for the evaluation. + /// + /// True if the value satisfies the comparison; otherwise, false. + /// + /// Thrown when is null. + /// Thrown when the comparison operator is not supported. + public static bool Like(this string value, string like, ComparisonOperator? likeCompare) { - /// - /// Determines if the specified string value matches a pattern based on the provided comparison operator. - /// - /// The string value to evaluate. - /// The pattern to compare against. - /// The comparison operator to use for the evaluation. - /// - /// True if the value satisfies the comparison; otherwise, false. - /// - /// Thrown when is null. - /// Thrown when the comparison operator is not supported. - public static bool Like(this string value, string like, ComparisonOperator? likeCompare) - { - if (value is null) - throw new ArgumentNullException(nameof(value)); - - if (like.IsEmpty()) - return true; + if (value is null) + throw new ArgumentNullException(nameof(value)); - return likeCompare switch - { - ComparisonOperator.In or null => value.ContainsIgnoreCase(like), - ComparisonOperator.Greater or ComparisonOperator.GreaterOrEqual => value.StartsWithIgnoreCase(like), - ComparisonOperator.Less or ComparisonOperator.LessOrEqual => value.EndsWithIgnoreCase(like), - ComparisonOperator.Equal => value.EqualsIgnoreCase(like), - ComparisonOperator.NotEqual => !value.ContainsIgnoreCase(like), - _ => throw new ArgumentOutOfRangeException(nameof(likeCompare)), - }; - } + if (like.IsEmpty()) + return true; - /// - /// Converts a pattern into an expression according to the specified comparison operator. - /// - /// The pattern to convert. - /// - /// The comparison operator that determines how the expression is formed. - /// Defaults to ComparisonOperator.In if null. - /// - /// A string expression representing the pattern. - /// Thrown when is empty. - /// Thrown when the NotEqual operator is used. - /// Thrown when the comparison operator is not supported. - public static string ToExpression(this string like, ComparisonOperator? likeCompare = default) + return likeCompare switch { - if (like.IsEmpty()) - throw new ArgumentNullException(nameof(like)); + ComparisonOperator.In or null => value.ContainsIgnoreCase(like), + ComparisonOperator.Greater or ComparisonOperator.GreaterOrEqual => value.StartsWithIgnoreCase(like), + ComparisonOperator.Less or ComparisonOperator.LessOrEqual => value.EndsWithIgnoreCase(like), + ComparisonOperator.Equal => value.EqualsIgnoreCase(like), + ComparisonOperator.NotEqual => !value.ContainsIgnoreCase(like), + _ => throw new ArgumentOutOfRangeException(nameof(likeCompare)), + }; + } - return likeCompare switch - { - ComparisonOperator.In or null => $"%{like}%", - ComparisonOperator.Greater or ComparisonOperator.GreaterOrEqual => $"{like}%", - ComparisonOperator.Less or ComparisonOperator.LessOrEqual => $"%{like}", - ComparisonOperator.Equal => like, - ComparisonOperator.NotEqual => throw new NotSupportedException(), - _ => throw new ArgumentOutOfRangeException(nameof(likeCompare)), - }; - } + /// + /// Converts a pattern into an expression according to the specified comparison operator. + /// + /// The pattern to convert. + /// + /// The comparison operator that determines how the expression is formed. + /// Defaults to ComparisonOperator.In if null. + /// + /// A string expression representing the pattern. + /// Thrown when is empty. + /// Thrown when the NotEqual operator is used. + /// Thrown when the comparison operator is not supported. + public static string ToExpression(this string like, ComparisonOperator? likeCompare = default) + { + if (like.IsEmpty()) + throw new ArgumentNullException(nameof(like)); + + return likeCompare switch + { + ComparisonOperator.In or null => $"%{like}%", + ComparisonOperator.Greater or ComparisonOperator.GreaterOrEqual => $"{like}%", + ComparisonOperator.Less or ComparisonOperator.LessOrEqual => $"%{like}", + ComparisonOperator.Equal => like, + ComparisonOperator.NotEqual => throw new NotSupportedException(), + _ => throw new ArgumentOutOfRangeException(nameof(likeCompare)), + }; } } \ No newline at end of file diff --git a/ComponentModel/ConvertibleObservableCollection.cs b/ComponentModel/ConvertibleObservableCollection.cs index d2ec58fa..5bd2cc67 100644 --- a/ComponentModel/ConvertibleObservableCollection.cs +++ b/ComponentModel/ConvertibleObservableCollection.cs @@ -1,3 +1,5 @@ +namespace Ecng.ComponentModel; + using System; using System.Collections; using System.Collections.Generic; @@ -6,8 +8,6 @@ using Ecng.Collections; -namespace Ecng.ComponentModel; - /// /// Collection which maps items to another type to display. /// diff --git a/ComponentModel/CustomObjectWrapper.cs b/ComponentModel/CustomObjectWrapper.cs index 2f5713b0..92c08500 100644 --- a/ComponentModel/CustomObjectWrapper.cs +++ b/ComponentModel/CustomObjectWrapper.cs @@ -1,165 +1,164 @@ -namespace Ecng.ComponentModel +namespace Ecng.ComponentModel; + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; + +using Ecng.Common; + +/// +/// Create instance. +/// +/// Parent chart element or indicator. +public abstract class CustomObjectWrapper(T obj) : Disposable, INotifyPropertyChanged, ICustomTypeDescriptor where T : class { - using System; - using System.Collections.Generic; - using System.ComponentModel; - using System.Linq; + /// + public event PropertyChangedEventHandler PropertyChanged; - using Ecng.Common; + /// + /// Call event. + /// + /// Member name. + protected virtual void OnPropertyChanged(string name) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); /// - /// Create instance. + /// Specialization of . /// - /// Parent chart element or indicator. - public abstract class CustomObjectWrapper(T obj) : Disposable, INotifyPropertyChanged, ICustomTypeDescriptor where T : class + /// + /// Create instance. + /// + protected class ProxyPropDescriptor(PropertyDescriptor orig, object owner) : NamedPropertyDescriptor(orig) { - /// - public event PropertyChangedEventHandler PropertyChanged; + private readonly PropertyDescriptor _orig = orig; /// - /// Call event. + /// Parent object. /// - /// Member name. - protected virtual void OnPropertyChanged(string name) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); + public object Owner { get; } = owner; - /// - /// Specialization of . - /// - /// - /// Create instance. - /// - protected class ProxyPropDescriptor(PropertyDescriptor orig, object owner) : NamedPropertyDescriptor(orig) - { - private readonly PropertyDescriptor _orig = orig; - - /// - /// Parent object. - /// - public object Owner { get; } = owner; - - /// - public override object GetValue(object c) => _orig.GetValue(c); - /// - public override void SetValue(object c, object value) => throw new NotSupportedException(); - - /// - public override bool CanResetValue(object c) => false; - /// - public override void ResetValue(object c) => throw new NotSupportedException(); - - /// - public override bool ShouldSerializeValue(object c) => false; - - /// - public override Type ComponentType => Owner.GetType(); - /// - public override bool IsReadOnly => true; - /// - public override Type PropertyType => _orig.PropertyType; - } + /// + public override object GetValue(object c) => _orig.GetValue(c); + /// + public override void SetValue(object c, object value) => throw new NotSupportedException(); - /// - /// Specialization of . - /// - /// - /// Create instance. - /// - protected class ProxyEventDescriptor(EventDescriptor orig, object owner) : EventDescriptor(orig) - { - private readonly EventDescriptor _orig = orig; - - /// - /// Parent object. - /// - public object Owner { get; } = owner; - - /// - public override void AddEventHandler(object component, Delegate value) => _orig.AddEventHandler(component, value); - /// - public override void RemoveEventHandler(object component, Delegate value) => _orig.RemoveEventHandler(component, value); - - /// - public override Type ComponentType => Owner.GetType(); - /// - public override Type EventType => _orig.EventType; - /// - public override bool IsMulticast => _orig.IsMulticast; - } + /// + public override bool CanResetValue(object c) => false; + /// + public override void ResetValue(object c) => throw new NotSupportedException(); + + /// + public override bool ShouldSerializeValue(object c) => false; + + /// + public override Type ComponentType => Owner.GetType(); + /// + public override bool IsReadOnly => true; + /// + public override Type PropertyType => _orig.PropertyType; + } + + /// + /// Specialization of . + /// + /// + /// Create instance. + /// + protected class ProxyEventDescriptor(EventDescriptor orig, object owner) : EventDescriptor(orig) + { + private readonly EventDescriptor _orig = orig; /// /// Parent object. /// - public T Obj { get; } = obj ?? throw new ArgumentNullException(nameof(obj)); + public object Owner { get; } = owner; + + /// + public override void AddEventHandler(object component, Delegate value) => _orig.AddEventHandler(component, value); + /// + public override void RemoveEventHandler(object component, Delegate value) => _orig.RemoveEventHandler(component, value); - object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd) => Obj; + /// + public override Type ComponentType => Owner.GetType(); + /// + public override Type EventType => _orig.EventType; + /// + public override bool IsMulticast => _orig.IsMulticast; + } - AttributeCollection ICustomTypeDescriptor.GetAttributes() => TypeDescriptor.GetAttributes(Obj, true); + /// + /// Parent object. + /// + public T Obj { get; } = obj ?? throw new ArgumentNullException(nameof(obj)); - string ICustomTypeDescriptor.GetClassName() => TypeDescriptor.GetClassName(Obj, true); + object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd) => Obj; - string ICustomTypeDescriptor.GetComponentName() => TypeDescriptor.GetComponentName(Obj, true); + AttributeCollection ICustomTypeDescriptor.GetAttributes() => TypeDescriptor.GetAttributes(Obj, true); - TypeConverter ICustomTypeDescriptor.GetConverter() => TypeDescriptor.GetConverter(Obj, true); + string ICustomTypeDescriptor.GetClassName() => TypeDescriptor.GetClassName(Obj, true); - EventDescriptor ICustomTypeDescriptor.GetDefaultEvent() => TypeDescriptor.GetDefaultEvent(Obj, true); + string ICustomTypeDescriptor.GetComponentName() => TypeDescriptor.GetComponentName(Obj, true); - PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty() => TypeDescriptor.GetDefaultProperty(Obj, true); + TypeConverter ICustomTypeDescriptor.GetConverter() => TypeDescriptor.GetConverter(Obj, true); - object ICustomTypeDescriptor.GetEditor(Type editorBaseType) => TypeDescriptor.GetEditor(Obj, editorBaseType, true); + EventDescriptor ICustomTypeDescriptor.GetDefaultEvent() => TypeDescriptor.GetDefaultEvent(Obj, true); - EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes) => ((ICustomTypeDescriptor)this).GetEvents(); + PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty() => TypeDescriptor.GetDefaultProperty(Obj, true); - PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes) => ((ICustomTypeDescriptor)this).GetProperties(); + object ICustomTypeDescriptor.GetEditor(Type editorBaseType) => TypeDescriptor.GetEditor(Obj, editorBaseType, true); - /// - /// Get property list from wrapped object. - /// - protected virtual IEnumerable OnGetEvents() - { - return Obj is null - ? null - : TypeDescriptor.GetEvents(Obj, true) - .OfType() - .Select(ed => new ProxyEventDescriptor(ed, this)); - } + EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes) => ((ICustomTypeDescriptor)this).GetEvents(); - /// - /// Get property list from wrapped object. - /// - protected virtual IEnumerable OnGetProperties() - { - return Obj is null - ? null - : TypeDescriptor.GetProperties(Obj, true) - .Typed() - .Select(pd => new ProxyPropDescriptor(pd, this)); - } + PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes) => ((ICustomTypeDescriptor)this).GetProperties(); - private EventDescriptorCollection _eventCollection; + /// + /// Get property list from wrapped object. + /// + protected virtual IEnumerable OnGetEvents() + { + return Obj is null + ? null + : TypeDescriptor.GetEvents(Obj, true) + .OfType() + .Select(ed => new ProxyEventDescriptor(ed, this)); + } - EventDescriptorCollection ICustomTypeDescriptor.GetEvents() - { - if(_eventCollection != null) - return _eventCollection; + /// + /// Get property list from wrapped object. + /// + protected virtual IEnumerable OnGetProperties() + { + return Obj is null + ? null + : TypeDescriptor.GetProperties(Obj, true) + .Typed() + .Select(pd => new ProxyPropDescriptor(pd, this)); + } - _eventCollection = new EventDescriptorCollection(OnGetEvents()?.ToArray() ?? []); + private EventDescriptorCollection _eventCollection; + EventDescriptorCollection ICustomTypeDescriptor.GetEvents() + { + if(_eventCollection != null) return _eventCollection; - } - private PropertyDescriptorCollection _propCollection; + _eventCollection = new EventDescriptorCollection(OnGetEvents()?.ToArray() ?? []); - PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties() - { - if(_propCollection != null) - return _propCollection; + return _eventCollection; + } - _propCollection = new PropertyDescriptorCollection(OnGetProperties()?.ToArray() ?? []); + private PropertyDescriptorCollection _propCollection; + PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties() + { + if(_propCollection != null) return _propCollection; - } - /// - public override string ToString() => Obj?.ToString(); + _propCollection = new PropertyDescriptorCollection(OnGetProperties()?.ToArray() ?? []); + + return _propCollection; } + + /// + public override string ToString() => Obj?.ToString(); } \ No newline at end of file diff --git a/ComponentModel/DispatcherNotifiableObject.cs b/ComponentModel/DispatcherNotifiableObject.cs index d73f579f..a201a907 100644 --- a/ComponentModel/DispatcherNotifiableObject.cs +++ b/ComponentModel/DispatcherNotifiableObject.cs @@ -1,85 +1,85 @@ -namespace Ecng.ComponentModel -{ - using System; - using System.Threading.Tasks; - using System.Collections.Generic; - using System.ComponentModel; - using System.Linq; +namespace Ecng.ComponentModel; - using Ecng.Collections; +using System; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; - class DispatcherNotifiableObjectTimer - { - private readonly TimeSpan _minInterval = TimeSpan.FromMilliseconds(100); - private TimeSpan _interval = TimeSpan.FromMilliseconds(1000); +using Ecng.Collections; + +class DispatcherNotifiableObjectTimer +{ + private readonly TimeSpan _minInterval = TimeSpan.FromMilliseconds(100); + private TimeSpan _interval = TimeSpan.FromMilliseconds(1000); - public event Action Tick; + public event Action Tick; - public TimeSpan Interval + public TimeSpan Interval + { + get => _interval; + set { - get => _interval; - set - { - if (value > _interval) - return; + if (value > _interval) + return; - if (value < _minInterval) - value = _minInterval; + if (value < _minInterval) + value = _minInterval; - _interval = value; - } + _interval = value; } + } - public DispatcherNotifiableObjectTimer() => Task.Run(TimerTask); + public DispatcherNotifiableObjectTimer() => Task.Run(TimerTask); - private async Task TimerTask() + private async Task TimerTask() + { + while (true) { - while (true) - { - await Task.Delay(_interval); - Tick?.Invoke(); - } - // ReSharper disable once FunctionNeverReturns + await Task.Delay(_interval); + Tick?.Invoke(); } - - private static readonly Lazy _instance = new(true); - public static DispatcherNotifiableObjectTimer Instance => _instance.Value; + // ReSharper disable once FunctionNeverReturns } - - /// - /// Forward notifications to dispatcher thread. - /// Multiple notifications for the same property may be forwarded only once. - /// - public class DispatcherNotifiableObject : CustomObjectWrapper - where T : class, INotifyPropertyChanged + + private static readonly Lazy _instance = new(true); + public static DispatcherNotifiableObjectTimer Instance => _instance.Value; +} + +/// +/// Forward notifications to dispatcher thread. +/// Multiple notifications for the same property may be forwarded only once. +/// +public class DispatcherNotifiableObject : CustomObjectWrapper + where T : class, INotifyPropertyChanged { - private static DispatcherNotifiableObjectTimer Timer => DispatcherNotifiableObjectTimer.Instance; + private static DispatcherNotifiableObjectTimer Timer => DispatcherNotifiableObjectTimer.Instance; - /// - /// - protected TimeSpan NotifyInterval + /// + /// + protected TimeSpan NotifyInterval { get => _notifyInterval; set { _notifyInterval = value; - Timer.Interval = value; + Timer.Interval = value; } } - private readonly IDispatcher _dispatcher; - private readonly SynchronizedSet _names = []; + private readonly IDispatcher _dispatcher; + private readonly SynchronizedSet _names = []; private DateTime _nextTime; private TimeSpan _notifyInterval; /// /// public DispatcherNotifiableObject(IDispatcher dispatcher, T obj) - : base(obj) + : base(obj) { - _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); - NotifyInterval = TimeSpan.FromMilliseconds(333); - Timer.Tick += NotifiableObjectGuiWrapperTimerOnTick; + _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); + NotifyInterval = TimeSpan.FromMilliseconds(333); + Timer.Tick += NotifiableObjectGuiWrapperTimerOnTick; Obj.PropertyChanged += (_, args) => _names.Add(args.PropertyName); } @@ -109,13 +109,13 @@ private void NotifiableObjectGuiWrapperTimerOnTick() if (names.Length == 0) return; - _dispatcher.InvokeAsync(() => names.ForEach(OnPropertyChanged)); + _dispatcher.InvokeAsync(() => names.ForEach(OnPropertyChanged)); } /// protected override void DisposeManaged() { - Timer.Tick -= NotifiableObjectGuiWrapperTimerOnTick; + Timer.Tick -= NotifiableObjectGuiWrapperTimerOnTick; base.DisposeManaged(); } @@ -128,14 +128,13 @@ protected override void DisposeManaged() protected override IEnumerable OnGetEvents() { var descriptor = TypeDescriptor - .GetEvents(this, true) - .OfType() - .First(ed => ed.Name == nameof(PropertyChanged)); + .GetEvents(this, true) + .OfType() + .First(ed => ed.Name == nameof(PropertyChanged)); return base.OnGetEvents() .Where(ed => ed.Name != nameof(PropertyChanged)) .Concat([descriptor]); } - } -} \ No newline at end of file + } \ No newline at end of file diff --git a/ComponentModel/DispatcherObservableCollection.cs b/ComponentModel/DispatcherObservableCollection.cs index 159a5e2e..f45f7855 100644 --- a/ComponentModel/DispatcherObservableCollection.cs +++ b/ComponentModel/DispatcherObservableCollection.cs @@ -1,368 +1,367 @@ -namespace Ecng.ComponentModel -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Linq; +namespace Ecng.ComponentModel; - using Ecng.Collections; - using Ecng.Common; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; - /// - /// The class represents a synchronized collection that can be used in WPF applications. - /// - public class DispatcherObservableCollection(IDispatcher dispatcher, IListEx items) : BaseObservableCollection, ISynchronizedCollection, IListEx, IList +using Ecng.Collections; +using Ecng.Common; + +/// +/// The class represents a synchronized collection that can be used in WPF applications. +/// +public class DispatcherObservableCollection(IDispatcher dispatcher, IListEx items) : BaseObservableCollection, ISynchronizedCollection, IListEx, IList +{ + private enum ActionTypes { - private enum ActionTypes + Add, + Remove, + Clear, + CopyTo, + Insert, + RemoveAt, + Set + } + + private class CollectionAction + { + public CollectionAction(ActionTypes type, params TItem[] items) { - Add, - Remove, - Clear, - CopyTo, - Insert, - RemoveAt, - Set + Type = type; + Items = items ?? throw new ArgumentNullException(nameof(items)); } - private class CollectionAction + public CollectionAction(ActionTypes type, int index, int count) { - public CollectionAction(ActionTypes type, params TItem[] items) - { - Type = type; - Items = items ?? throw new ArgumentNullException(nameof(items)); - } + Type = type; + Index = index; + Count = count; + } - public CollectionAction(ActionTypes type, int index, int count) - { - Type = type; - Index = index; - Count = count; - } + public ActionTypes Type { get; } + public TItem[] Items { get; } + public int Index { get; set; } + public int Count { get; } + } - public ActionTypes Type { get; } - public TItem[] Items { get; } - public int Index { get; set; } - public int Count { get; } - } + private readonly SynchronizedList _syncCopy = []; + private readonly Queue _pendingActions = []; + private bool _isTimerStarted; - private readonly SynchronizedList _syncCopy = []; - private readonly Queue _pendingActions = []; - private bool _isTimerStarted; + /// + /// + public event Action BeforeUpdate; + /// + /// + public event Action AfterUpdate; - /// - /// - public event Action BeforeUpdate; - /// - /// - public event Action AfterUpdate; + /// + /// + public IListEx Items { get; } = items ?? throw new ArgumentNullException(nameof(items)); - /// - /// - public IListEx Items { get; } = items ?? throw new ArgumentNullException(nameof(items)); + /// + /// + public IDispatcher Dispatcher { get; } = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); - /// - /// - public IDispatcher Dispatcher { get; } = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); + /// + /// + public event Action> AddedRange + { + add => Items.AddedRange += value; + remove => Items.AddedRange -= value; + } - /// - /// - public event Action> AddedRange - { - add => Items.AddedRange += value; - remove => Items.AddedRange -= value; - } + /// + /// + public event Action> RemovedRange + { + add => Items.RemovedRange += value; + remove => Items.RemovedRange -= value; + } - /// - /// - public event Action> RemovedRange - { - add => Items.RemovedRange += value; - remove => Items.RemovedRange -= value; - } + /// + /// + public virtual void AddRange(IEnumerable items) + { + var arr = items.ToArray(); - /// - /// - public virtual void AddRange(IEnumerable items) - { - var arr = items.ToArray(); + _syncCopy.AddRange(arr); - _syncCopy.AddRange(arr); + if (Dispatcher.CheckAccess()) + Items.AddRange(arr); + else + AddAction(new(ActionTypes.Add, arr)); - if (Dispatcher.CheckAccess()) - Items.AddRange(arr); - else - AddAction(new(ActionTypes.Add, arr)); + CheckCount(); + } - CheckCount(); - } + /// + /// Remove range of items. + /// + /// Items. + public virtual void RemoveRange(IEnumerable items) + { + var arr = items.ToArray(); - /// - /// Remove range of items. - /// - /// Items. - public virtual void RemoveRange(IEnumerable items) - { - var arr = items.ToArray(); + _syncCopy.RemoveRange(arr); - _syncCopy.RemoveRange(arr); + if (Dispatcher.CheckAccess()) + Items.RemoveRange(arr); + else + AddAction(new(ActionTypes.Remove, arr)); + } - if (Dispatcher.CheckAccess()) - Items.RemoveRange(arr); - else - AddAction(new(ActionTypes.Remove, arr)); - } + /// + public override int RemoveRange(int index, int count) + { + if (index < 0) + throw new ArgumentOutOfRangeException(nameof(index)); - /// - public override int RemoveRange(int index, int count) - { - if (index < 0) - throw new ArgumentOutOfRangeException(nameof(index)); + if (count <= 0) + throw new ArgumentOutOfRangeException(nameof(count)); - if (count <= 0) - throw new ArgumentOutOfRangeException(nameof(count)); + var retVal = _syncCopy.RemoveRange(index, count); - var retVal = _syncCopy.RemoveRange(index, count); + if (Dispatcher.CheckAccess()) + Items.RemoveRange(index, count); + else + AddAction(new(ActionTypes.Remove, index, count)); - if (Dispatcher.CheckAccess()) - Items.RemoveRange(index, count); - else - AddAction(new(ActionTypes.Remove, index, count)); + return retVal; + } - return retVal; - } + /// + public IEnumerator GetEnumerator() + => _syncCopy.GetEnumerator(); - /// - public IEnumerator GetEnumerator() - => _syncCopy.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() + => GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() - => GetEnumerator(); + /// + public virtual void Add(TItem item) + { + _syncCopy.Add(item); - /// - public virtual void Add(TItem item) - { - _syncCopy.Add(item); + if (Dispatcher.CheckAccess()) + Items.Add(item); + else + AddAction(new(ActionTypes.Add, item)); + + CheckCount(); + } + /// + public virtual bool Remove(TItem item) + { + var removed = _syncCopy.Remove(item); + + if (removed) + { if (Dispatcher.CheckAccess()) - Items.Add(item); + Items.Remove(item); else - AddAction(new(ActionTypes.Add, item)); - - CheckCount(); + AddAction(new(ActionTypes.Remove, item)); } - /// - public virtual bool Remove(TItem item) - { - var removed = _syncCopy.Remove(item); + return removed; + } - if (removed) - { - if (Dispatcher.CheckAccess()) - Items.Remove(item); - else - AddAction(new(ActionTypes.Remove, item)); - } + int IList.Add(object value) + { + Add((TItem)value); + return Count - 1; + } - return removed; - } + bool IList.Contains(object value) + => Contains((TItem)value); - int IList.Add(object value) - { - Add((TItem)value); - return Count - 1; - } + /// + public virtual void Clear() + { + _syncCopy.Clear(); - bool IList.Contains(object value) - => Contains((TItem)value); + if (Dispatcher.CheckAccess()) + Items.Clear(); + else + AddAction(new(ActionTypes.Clear)); + } - /// - public virtual void Clear() - { - _syncCopy.Clear(); + int IList.IndexOf(object value) + => IndexOf((TItem)value); - if (Dispatcher.CheckAccess()) - Items.Clear(); - else - AddAction(new(ActionTypes.Clear)); - } + void IList.Insert(int index, object value) + => Insert(index, (TItem)value); - int IList.IndexOf(object value) - => IndexOf((TItem)value); + void IList.Remove(object value) + => Remove((TItem)value); - void IList.Insert(int index, object value) - => Insert(index, (TItem)value); + /// + public bool Contains(TItem item) + => _syncCopy.Contains(item); - void IList.Remove(object value) - => Remove((TItem)value); + /// + public void CopyTo(TItem[] array, int arrayIndex) + => _syncCopy.CopyTo(array, arrayIndex); - /// - public bool Contains(TItem item) - => _syncCopy.Contains(item); + void ICollection.CopyTo(Array array, int index) + => CopyTo((TItem[])array, index); - /// - public void CopyTo(TItem[] array, int arrayIndex) - => _syncCopy.CopyTo(array, arrayIndex); + /// + public override int Count => _syncCopy.Count; - void ICollection.CopyTo(Array array, int index) - => CopyTo((TItem[])array, index); + object ICollection.SyncRoot => SyncRoot; - /// - public override int Count => _syncCopy.Count; + bool ICollection.IsSynchronized => true; - object ICollection.SyncRoot => SyncRoot; + /// + public bool IsReadOnly => false; - bool ICollection.IsSynchronized => true; + bool IList.IsFixedSize => false; - /// - public bool IsReadOnly => false; + /// + public int IndexOf(TItem item) + => _syncCopy.IndexOf(item); - bool IList.IsFixedSize => false; + /// + public void Insert(int index, TItem item) + { + _syncCopy.Insert(index, item); - /// - public int IndexOf(TItem item) - => _syncCopy.IndexOf(item); + if (Dispatcher.CheckAccess()) + Items.Insert(index, item); + else + AddAction(new(ActionTypes.Insert, item) { Index = index }); + } - /// - public void Insert(int index, TItem item) - { - _syncCopy.Insert(index, item); + /// + public void RemoveAt(int index) + { + _syncCopy.RemoveAt(index); - if (Dispatcher.CheckAccess()) - Items.Insert(index, item); - else - AddAction(new(ActionTypes.Insert, item) { Index = index }); - } + if (Dispatcher.CheckAccess()) + Items.RemoveAt(index); + else + AddAction(new(ActionTypes.RemoveAt) { Index = index }); + } - /// - public void RemoveAt(int index) + object IList.this[int index] + { + get => this[index]; + set => this[index] = (TItem)value; + } + + /// + public TItem this[int index] + { + get => _syncCopy[index]; + set { - _syncCopy.RemoveAt(index); + _syncCopy[index] = value; if (Dispatcher.CheckAccess()) - Items.RemoveAt(index); + Items[index] = value; else - AddAction(new(ActionTypes.RemoveAt) { Index = index }); + AddAction(new(ActionTypes.Set, value) { Index = index }); } + } - object IList.this[int index] - { - get => this[index]; - set => this[index] = (TItem)value; - } + private void AddAction(CollectionAction item) + { + if (item == null) + throw new ArgumentNullException(nameof(item)); - /// - public TItem this[int index] + lock (SyncRoot) { - get => _syncCopy[index]; - set - { - _syncCopy[index] = value; + _pendingActions.Enqueue(item); - if (Dispatcher.CheckAccess()) - Items[index] = value; - else - AddAction(new(ActionTypes.Set, value) { Index = index }); - } + if (_isTimerStarted) + return; + + _isTimerStarted = true; } - private void AddAction(CollectionAction item) - { - if (item == null) - throw new ArgumentNullException(nameof(item)); + ThreadingHelper + .Timer(OnFlush) + .Interval(TimeSpan.FromMilliseconds(300), new TimeSpan(-1)); + } + + private void OnFlush() + { + List pendingActions; + var hasClear = false; + Exception error = null; + try + { lock (SyncRoot) { - _pendingActions.Enqueue(item); + pendingActions = [.. _pendingActions]; + _pendingActions.Clear(); + _isTimerStarted = false; + } - if (_isTimerStarted) - return; + for (var i = 0; i < pendingActions.Count; i++) + { + if (pendingActions[i].Type != ActionTypes.Clear) + continue; - _isTimerStarted = true; + pendingActions.RemoveRange(0, i + 1); + hasClear = true; + i = -1; } - - ThreadingHelper - .Timer(OnFlush) - .Interval(TimeSpan.FromMilliseconds(300), new TimeSpan(-1)); } - - private void OnFlush() + catch (Exception ex) { - List pendingActions; - var hasClear = false; - Exception error = null; - - try - { - lock (SyncRoot) - { - pendingActions = [.. _pendingActions]; - _pendingActions.Clear(); - _isTimerStarted = false; - } + error = ex; + pendingActions = []; + } - for (var i = 0; i < pendingActions.Count; i++) - { - if (pendingActions[i].Type != ActionTypes.Clear) - continue; + Dispatcher.InvokeAsync(() => + { + BeforeUpdate?.Invoke(); - pendingActions.RemoveRange(0, i + 1); - hasClear = true; - i = -1; - } - } - catch (Exception ex) - { - error = ex; - pendingActions = []; - } + if (hasClear) + Items.Clear(); - Dispatcher.InvokeAsync(() => + foreach (var action in pendingActions) { - BeforeUpdate?.Invoke(); - - if (hasClear) - Items.Clear(); - - foreach (var action in pendingActions) + switch (action.Type) { - switch (action.Type) - { - case ActionTypes.Add: - Items.AddRange(action.Items); - break; - case ActionTypes.Remove: - if (action.Items != null) - Items.RemoveRange(action.Items); - else - Items.RemoveRange(action.Index, action.Count); - break; - case ActionTypes.CopyTo: - Items.CopyTo(action.Items, action.Index); - break; - case ActionTypes.Insert: - Items.Insert(action.Index, action.Items[0]); - break; - case ActionTypes.RemoveAt: - Items.RemoveAt(action.Index); - break; - case ActionTypes.Set: - Items[action.Index] = action.Items[0]; - break; - default: - throw new ArgumentOutOfRangeException(action.Type.To()); - } + case ActionTypes.Add: + Items.AddRange(action.Items); + break; + case ActionTypes.Remove: + if (action.Items != null) + Items.RemoveRange(action.Items); + else + Items.RemoveRange(action.Index, action.Count); + break; + case ActionTypes.CopyTo: + Items.CopyTo(action.Items, action.Index); + break; + case ActionTypes.Insert: + Items.Insert(action.Index, action.Items[0]); + break; + case ActionTypes.RemoveAt: + Items.RemoveAt(action.Index); + break; + case ActionTypes.Set: + Items[action.Index] = action.Items[0]; + break; + default: + throw new ArgumentOutOfRangeException(action.Type.To()); } + } - AfterUpdate?.Invoke(); - - if (error != null) - throw error; - }); - } + AfterUpdate?.Invoke(); - /// - /// - public SyncObject SyncRoot => _syncCopy.SyncRoot; + if (error != null) + throw error; + }); } + + /// + /// + public SyncObject SyncRoot => _syncCopy.SyncRoot; } diff --git a/ComponentModel/DocAttribute.cs b/ComponentModel/DocAttribute.cs index 1ebdc5de..ae8ca09b 100644 --- a/ComponentModel/DocAttribute.cs +++ b/ComponentModel/DocAttribute.cs @@ -1,21 +1,20 @@ -namespace Ecng.ComponentModel -{ - using System; +namespace Ecng.ComponentModel; + +using System; - using Ecng.Common; +using Ecng.Common; +/// +/// Online doc url attribute. +/// +/// +/// Initializes a new instance of the . +/// +/// Online doc url. +public class DocAttribute(string docUrl) : Attribute +{ /// - /// Online doc url attribute. + /// Online doc url. /// - /// - /// Initializes a new instance of the . - /// - /// Online doc url. - public class DocAttribute(string docUrl) : Attribute - { - /// - /// Online doc url. - /// - public string DocUrl { get; } = docUrl.ThrowIfEmpty(nameof(docUrl)); - } + public string DocUrl { get; } = docUrl.ThrowIfEmpty(nameof(docUrl)); } \ No newline at end of file diff --git a/ComponentModel/EditorExtensionAttribute.cs b/ComponentModel/EditorExtensionAttribute.cs index c9a3c2ec..9d4cea1d 100644 --- a/ComponentModel/EditorExtensionAttribute.cs +++ b/ComponentModel/EditorExtensionAttribute.cs @@ -1,31 +1,30 @@ -namespace Ecng.ComponentModel -{ - using System; +namespace Ecng.ComponentModel; + +using System; +/// +/// Specifies editor extension options for enums and properties. +/// +[AttributeUsage(AttributeTargets.Enum | AttributeTargets.Property)] +public class EditorExtensionAttribute : Attribute +{ /// - /// Specifies editor extension options for enums and properties. + /// Gets or sets a value indicating whether auto-complete is enabled. /// - [AttributeUsage(AttributeTargets.Enum | AttributeTargets.Property)] - public class EditorExtensionAttribute : Attribute - { - /// - /// Gets or sets a value indicating whether auto-complete is enabled. - /// - public bool AutoComplete { get; set; } + public bool AutoComplete { get; set; } - /// - /// Gets or sets a value indicating whether the items should be sorted. - /// - public bool Sorted { get; set; } + /// + /// Gets or sets a value indicating whether the items should be sorted. + /// + public bool Sorted { get; set; } - /// - /// Gets or sets a value indicating whether obsolete items should be included. - /// - public bool IncludeObsolete { get; set; } + /// + /// Gets or sets a value indicating whether obsolete items should be included. + /// + public bool IncludeObsolete { get; set; } - /// - /// Gets or sets a value indicating whether the count of selected items should be displayed. - /// - public bool ShowSelectedItemsCount { get; set; } - } + /// + /// Gets or sets a value indicating whether the count of selected items should be displayed. + /// + public bool ShowSelectedItemsCount { get; set; } } \ No newline at end of file diff --git a/ComponentModel/EntityProperty.cs b/ComponentModel/EntityProperty.cs index c83ce97d..e5fec3e6 100644 --- a/ComponentModel/EntityProperty.cs +++ b/ComponentModel/EntityProperty.cs @@ -1,54 +1,53 @@ -namespace Ecng.ComponentModel +namespace Ecng.ComponentModel; + +using System.Collections.Generic; + +/// +/// Represents an entity property with hierarchical structure. +/// +public class EntityProperty { - using System.Collections.Generic; + /// + /// Gets or sets the unique name of the property. + /// + public string Name { get; set; } + + /// + /// Gets or sets the display name of the property. + /// + public string DisplayName { get; set; } + + /// + /// Gets or sets the description of the property. + /// + public string Description { get; set; } + + /// + /// Gets or sets the parent property in the hierarchy. + /// + public EntityProperty Parent { get; set; } + + /// + /// Gets or sets the collection of child properties. + /// + public IEnumerable Properties { get; set; } + + /// + /// Gets the full display name, which includes the names of the parent properties. + /// + public string FullDisplayName => Parent is null ? DisplayName : $"{Parent.FullDisplayName} -> {DisplayName}"; + + /// + /// Gets the name of the parent property. Returns an empty string if there is no parent. + /// + public string ParentName => Parent is null ? string.Empty : Parent.Name; /// - /// Represents an entity property with hierarchical structure. + /// Returns a string that represents the current entity property. /// - public class EntityProperty + /// A string representation of the entity property including its name and full display name. + public override string ToString() { - /// - /// Gets or sets the unique name of the property. - /// - public string Name { get; set; } - - /// - /// Gets or sets the display name of the property. - /// - public string DisplayName { get; set; } - - /// - /// Gets or sets the description of the property. - /// - public string Description { get; set; } - - /// - /// Gets or sets the parent property in the hierarchy. - /// - public EntityProperty Parent { get; set; } - - /// - /// Gets or sets the collection of child properties. - /// - public IEnumerable Properties { get; set; } - - /// - /// Gets the full display name, which includes the names of the parent properties. - /// - public string FullDisplayName => Parent is null ? DisplayName : $"{Parent.FullDisplayName} -> {DisplayName}"; - - /// - /// Gets the name of the parent property. Returns an empty string if there is no parent. - /// - public string ParentName => Parent is null ? string.Empty : Parent.Name; - - /// - /// Returns a string that represents the current entity property. - /// - /// A string representation of the entity property including its name and full display name. - public override string ToString() - { - return $"{Name} ({FullDisplayName})"; - } + return $"{Name} ({FullDisplayName})"; } } diff --git a/ComponentModel/EntityPropertyHelper.cs b/ComponentModel/EntityPropertyHelper.cs index 2d4b57a2..a5ce6704 100644 --- a/ComponentModel/EntityPropertyHelper.cs +++ b/ComponentModel/EntityPropertyHelper.cs @@ -1,287 +1,286 @@ -namespace Ecng.ComponentModel -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Linq; - using System.Reflection; +namespace Ecng.ComponentModel; + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; - using Ecng.Common; - using Ecng.Reflection; +using Ecng.Common; +using Ecng.Reflection; +/// +/// Provides helper methods for retrieving entity properties from a specified type. +/// +public static class EntityPropertyHelper +{ /// - /// Provides helper methods for retrieving entity properties from a specified type. + /// Retrieves the entity properties for the specified type. /// - public static class EntityPropertyHelper + /// The type to retrieve entity properties from. + /// An optional filter to apply to the property information. + /// An enumerable collection of . + public static IEnumerable GetEntityProperties(this Type type, Func filter = null) { - /// - /// Retrieves the entity properties for the specified type. - /// - /// The type to retrieve entity properties from. - /// An optional filter to apply to the property information. - /// An enumerable collection of . - public static IEnumerable GetEntityProperties(this Type type, Func filter = null) - { - return type.GetEntityProperties(null, filter); - } - - /// - /// Retrieves the entity properties for the specified type with a specified parent entity property. - /// - /// The type to retrieve entity properties from. - /// The parent in the hierarchy. - /// An optional filter to apply to the property information. - /// An enumerable collection of . - public static IEnumerable GetEntityProperties(this Type type, EntityProperty parent, Func filter = null) - { - return type.GetEntityProperties(parent, [], filter ?? (p => true)); - } + return type.GetEntityProperties(null, filter); + } - private static IEnumerable GetEntityProperties(this Type type, EntityProperty parent, HashSet processed, Func filter) - { - if (processed.Contains(type)) - yield break; + /// + /// Retrieves the entity properties for the specified type with a specified parent entity property. + /// + /// The type to retrieve entity properties from. + /// The parent in the hierarchy. + /// An optional filter to apply to the property information. + /// An enumerable collection of . + public static IEnumerable GetEntityProperties(this Type type, EntityProperty parent, Func filter = null) + { + return type.GetEntityProperties(parent, [], filter ?? (p => true)); + } - type = type.GetUnderlyingType() ?? type; + private static IEnumerable GetEntityProperties(this Type type, EntityProperty parent, HashSet processed, Func filter) + { + if (processed.Contains(type)) + yield break; - var propertyInfos = type - .GetMembers(BindingFlags.Public | BindingFlags.Instance) - .Where(filter); + type = type.GetUnderlyingType() ?? type; - var names = new HashSet(); + var propertyInfos = type + .GetMembers(BindingFlags.Public | BindingFlags.Instance) + .Where(filter); - processed.Add(type); + var names = new HashSet(); - foreach (var pi in propertyInfos) - { - var name = (parent != null ? parent.Name + "." : string.Empty) + pi.Name; + processed.Add(type); - if (!names.Add(name)) - continue; + foreach (var pi in propertyInfos) + { + var name = (parent != null ? parent.Name + "." : string.Empty) + pi.Name; - var prop = new EntityProperty - { - Name = name, - Parent = parent, - DisplayName = pi.GetDisplayName(), - Description = pi.GetDescription() - }; + if (!names.Add(name)) + continue; - var propType = pi.PropertyType; + var prop = new EntityProperty + { + Name = name, + Parent = parent, + DisplayName = pi.GetDisplayName(), + Description = pi.GetDescription() + }; - if (!propType.IsPrimitive()) - { - prop.Properties = GetEntityProperties(propType, prop, processed, filter); - } + var propType = pi.PropertyType; - yield return prop; + if (!propType.IsPrimitive()) + { + prop.Properties = GetEntityProperties(propType, prop, processed, filter); } - processed.Remove(type); + yield return prop; } - /// - /// Gets the type of a nested property specified by its name. - /// - /// The type that contains the property. - /// The dot-separated name of the property. - /// An optional function to retrieve the type of a virtual property. - /// The of the property if found; otherwise, null. - public static Type GetPropType(this Type type, string name, Func getVirtualProp = null) - { - if (type is null) - throw new ArgumentNullException(nameof(type)); + processed.Remove(type); + } - if (name is null) - throw new ArgumentNullException(nameof(name)); + /// + /// Gets the type of a nested property specified by its name. + /// + /// The type that contains the property. + /// The dot-separated name of the property. + /// An optional function to retrieve the type of a virtual property. + /// The of the property if found; otherwise, null. + public static Type GetPropType(this Type type, string name, Func getVirtualProp = null) + { + if (type is null) + throw new ArgumentNullException(nameof(type)); - getVirtualProp ??= (t, n) => null; + if (name is null) + throw new ArgumentNullException(nameof(name)); - type = type.GetUnderlyingType() ?? type; + getVirtualProp ??= (t, n) => null; - foreach (var p in name.Split('.')) - { - var part = p; + type = type.GetUnderlyingType() ?? type; - var brIdx = part.IndexOf('['); - var index = brIdx != -1 ? part.Substring(brIdx + 1, part.IndexOf(']') - (brIdx + 1)) : null; + foreach (var p in name.Split('.')) + { + var part = p; - if (index is not null) - part = part.Substring(0, brIdx); + var brIdx = part.IndexOf('['); + var index = brIdx != -1 ? part.Substring(brIdx + 1, part.IndexOf(']') - (brIdx + 1)) : null; - var info = type.GetProperty(part); + if (index is not null) + part = part.Substring(0, brIdx); - if (info is null) - { - var virtualPropType = getVirtualProp(type, part); + var info = type.GetProperty(part); - if (virtualPropType is null) - return null; + if (info is null) + { + var virtualPropType = getVirtualProp(type, part); - type = virtualPropType; - } - else - type = info.PropertyType; + if (virtualPropType is null) + return null; - type = type.GetUnderlyingType() ?? type; + type = virtualPropType; + } + else + type = info.PropertyType; - if (type is not null && index is not null) + type = type.GetUnderlyingType() ?? type; + + if (type is not null && index is not null) + { + if (type.Is()) { - if (type.Is()) - { - type = type.GetItemType(); - } - else if (type.Is()) - { - type = typeof(object); - } - else if (type.GetGenericType(typeof(IDictionary<,>)) is not null) - { - type = type.GetGenericArguments()[1]; - } - else - return null; + type = type.GetItemType(); + } + else if (type.Is()) + { + type = typeof(object); + } + else if (type.GetGenericType(typeof(IDictionary<,>)) is not null) + { + type = type.GetGenericArguments()[1]; } + else + return null; } - - return type; } - /// - /// Gets the value of a nested property from an object. - /// - /// The object to retrieve the value from. - /// The dot-separated name of the property. - /// An optional function to retrieve the value of a virtual property. - /// An optional dictionary of variables for indexing. - /// The value of the property if found; otherwise, null. - public static object GetPropValue(this object entity, string name, Func getVirtualProp = null, IDictionary vars = null) - { - var value = entity; + return type; + } - getVirtualProp ??= (t, n) => null; + /// + /// Gets the value of a nested property from an object. + /// + /// The object to retrieve the value from. + /// The dot-separated name of the property. + /// An optional function to retrieve the value of a virtual property. + /// An optional dictionary of variables for indexing. + /// The value of the property if found; otherwise, null. + public static object GetPropValue(this object entity, string name, Func getVirtualProp = null, IDictionary vars = null) + { + var value = entity; - foreach (var p in name.Split('.')) - { - var part = p; + getVirtualProp ??= (t, n) => null; - if (value is null) - return null; + foreach (var p in name.Split('.')) + { + var part = p; - var brIdx = part.IndexOf('['); - var index = brIdx != -1 ? part.Substring(brIdx + 1, part.IndexOf(']') - (brIdx + 1)) : null; + if (value is null) + return null; - if (index is not null) - part = part.Substring(0, brIdx); + var brIdx = part.IndexOf('['); + var index = brIdx != -1 ? part.Substring(brIdx + 1, part.IndexOf(']') - (brIdx + 1)) : null; - var info = value.GetType().GetProperty(part); + if (index is not null) + part = part.Substring(0, brIdx); - if (info is null) - { - var virtualPropValue = getVirtualProp(value, part); + var info = value.GetType().GetProperty(part); - if (virtualPropValue is null) - return null; + if (info is null) + { + var virtualPropValue = getVirtualProp(value, part); - value = virtualPropValue; - } - else - value = info.GetValue(value, null); + if (virtualPropValue is null) + return null; + + value = virtualPropValue; + } + else + value = info.GetValue(value, null); - if (value is not null && index is not null) + if (value is not null && index is not null) + { + if (value is IList list) { - if (value is IList list) + if (!int.TryParse(index, out var i)) { - if (!int.TryParse(index, out var i)) - { - if (vars is null) - throw new InvalidOperationException($"{index} is not index."); - - i = vars[index].To(); - } - - if (i < 0 || i >= list.Count) - return null; + if (vars is null) + throw new InvalidOperationException($"{index} is not index."); - value = list[i]; + i = vars[index].To(); } - else if (value is IDictionary dict) - { - object key = index; - var type = dict.GetType(); + if (i < 0 || i >= list.Count) + return null; - if (type.IsGenericType) - { - var argTypes = type.GetGenericArguments(); - key = key.To(argTypes[0]); - } + value = list[i]; + } + else if (value is IDictionary dict) + { + object key = index; - if (!dict.Contains(key)) - return null; + var type = dict.GetType(); - value = dict[key]; + if (type.IsGenericType) + { + var argTypes = type.GetGenericArguments(); + key = key.To(argTypes[0]); } - else + + if (!dict.Contains(key)) return null; + + value = dict[key]; } + else + return null; } - - return value; } - /// - /// Retrieves variable names from a nested property path for the specified type. - /// - /// The type that contains the property. - /// The dot-separated property path. - /// An optional function to retrieve the type of a virtual property. - /// An enumerable collection of variable names present in the property path. - public static IEnumerable GetVars(this Type type, string name, Func getVirtualProp = null) - { - if (type is null) - throw new ArgumentNullException(nameof(type)); + return value; + } - if (name is null) - throw new ArgumentNullException(nameof(name)); + /// + /// Retrieves variable names from a nested property path for the specified type. + /// + /// The type that contains the property. + /// The dot-separated property path. + /// An optional function to retrieve the type of a virtual property. + /// An enumerable collection of variable names present in the property path. + public static IEnumerable GetVars(this Type type, string name, Func getVirtualProp = null) + { + if (type is null) + throw new ArgumentNullException(nameof(type)); - getVirtualProp ??= (t, n) => null; + if (name is null) + throw new ArgumentNullException(nameof(name)); - type = type.GetUnderlyingType() ?? type; + getVirtualProp ??= (t, n) => null; - foreach (var p in name.Split('.')) - { - var part = p; + type = type.GetUnderlyingType() ?? type; - var brIdx = part.IndexOf('['); - var index = brIdx != -1 ? part.Substring(brIdx + 1, part.IndexOf(']') - (brIdx + 1)) : null; + foreach (var p in name.Split('.')) + { + var part = p; - if (index is not null) - part = part.Substring(0, brIdx); + var brIdx = part.IndexOf('['); + var index = brIdx != -1 ? part.Substring(brIdx + 1, part.IndexOf(']') - (brIdx + 1)) : null; - var info = type.GetProperty(part); + if (index is not null) + part = part.Substring(0, brIdx); - if (info is null) - { - var virtualPropType = getVirtualProp(type, part); + var info = type.GetProperty(part); - if (virtualPropType is null) - yield break; + if (info is null) + { + var virtualPropType = getVirtualProp(type, part); - type = virtualPropType; - } - else - type = info.PropertyType; + if (virtualPropType is null) + yield break; - type = type.GetUnderlyingType() ?? type; + type = virtualPropType; + } + else + type = info.PropertyType; + + type = type.GetUnderlyingType() ?? type; - if (type is not null && index is not null) + if (type is not null && index is not null) + { + if (type.Is()) { - if (type.Is()) - { - if (!int.TryParse(index, out _)) - yield return index; - } + if (!int.TryParse(index, out _)) + yield return index; } } } diff --git a/ComponentModel/Extensions.cs b/ComponentModel/Extensions.cs index b3c5853d..5e990ab1 100644 --- a/ComponentModel/Extensions.cs +++ b/ComponentModel/Extensions.cs @@ -1,676 +1,675 @@ -namespace Ecng.ComponentModel +namespace Ecng.ComponentModel; + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; + +using Ecng.Collections; +using Ecng.Common; +using Ecng.Serialization; +using Ecng.Localization; + +/// +/// Provides a collection of extension methods for components, attributes, and debugging. +/// +public static class Extensions { - using System; - using System.Collections.Generic; - using System.ComponentModel; - using System.ComponentModel.DataAnnotations; - using System.Linq; - using System.Reflection; - using System.Runtime.InteropServices; - - using Ecng.Collections; - using Ecng.Common; - using Ecng.Serialization; - using Ecng.Localization; - /// - /// Provides a collection of extension methods for components, attributes, and debugging. + /// Retrieves the display name for the given attribute provider using DisplayAttribute or DisplayNameAttribute. /// - public static class Extensions + /// The custom attribute provider. + /// The default value if no display name is found. + /// The display name. + public static string GetDisplayName(this ICustomAttributeProvider provider, string defaultValue = null) { - /// - /// Retrieves the display name for the given attribute provider using DisplayAttribute or DisplayNameAttribute. - /// - /// The custom attribute provider. - /// The default value if no display name is found. - /// The display name. - public static string GetDisplayName(this ICustomAttributeProvider provider, string defaultValue = null) - { - if (provider is Assembly asm) - return asm.GetAttribute()?.Title; - - var dpAttr = provider.GetAttribute(); + if (provider is Assembly asm) + return asm.GetAttribute()?.Title; - if (dpAttr?.Name is null) - { - var nameAttr = provider.GetAttribute(); - return nameAttr is null ? defaultValue ?? provider.GetTypeName() : nameAttr.DisplayName; - } + var dpAttr = provider.GetAttribute(); - return dpAttr.GetName(); + if (dpAttr?.Name is null) + { + var nameAttr = provider.GetAttribute(); + return nameAttr is null ? defaultValue ?? provider.GetTypeName() : nameAttr.DisplayName; } - /// - /// Retrieves the display name for the specified property descriptor using DisplayAttribute or DisplayNameAttribute. - /// - /// The property descriptor. - /// The default value if no display name is found. - /// The display name. - public static string GetDisplayName(this PropertyDescriptor pd, string defaultValue = null) + return dpAttr.GetName(); + } + + /// + /// Retrieves the display name for the specified property descriptor using DisplayAttribute or DisplayNameAttribute. + /// + /// The property descriptor. + /// The default value if no display name is found. + /// The display name. + public static string GetDisplayName(this PropertyDescriptor pd, string defaultValue = null) + { + foreach(var a in pd.Attributes) { - foreach(var a in pd.Attributes) + switch (a) { - switch (a) - { - case DisplayAttribute da: - return da.GetName(); - case DisplayNameAttribute dna: - return dna.DisplayName; - } + case DisplayAttribute da: + return da.GetName(); + case DisplayNameAttribute dna: + return dna.DisplayName; } - - return defaultValue ?? pd.PropertyType.Name; } - /// - /// Retrieves the description for the given attribute provider using DisplayAttribute or DescriptionAttribute. - /// - /// The custom attribute provider. - /// The default description if none is found. - /// The description string. - public static string GetDescription(this ICustomAttributeProvider provider, string defaultValue = null) - { - if (provider is Assembly asm) - return asm.GetAttribute()?.Description; + return defaultValue ?? pd.PropertyType.Name; + } - var dpAttr = provider.GetAttribute(); + /// + /// Retrieves the description for the given attribute provider using DisplayAttribute or DescriptionAttribute. + /// + /// The custom attribute provider. + /// The default description if none is found. + /// The description string. + public static string GetDescription(this ICustomAttributeProvider provider, string defaultValue = null) + { + if (provider is Assembly asm) + return asm.GetAttribute()?.Description; - if (dpAttr?.Description is null) - { - var descrAttr = provider.GetAttribute(); - return descrAttr is null ? defaultValue ?? provider.GetTypeName() : descrAttr.Description; - } + var dpAttr = provider.GetAttribute(); - return dpAttr.GetDescription(); + if (dpAttr?.Description is null) + { + var descrAttr = provider.GetAttribute(); + return descrAttr is null ? defaultValue ?? provider.GetTypeName() : descrAttr.Description; } - /// - /// Retrieves the category name for the given attribute provider using DisplayAttribute or CategoryAttribute. - /// - /// The custom attribute provider. - /// The default category if none is found. - /// The category name. - public static string GetCategory(this ICustomAttributeProvider provider, string defaultValue = null) - { - var dpAttr = provider.GetAttribute(); + return dpAttr.GetDescription(); + } - if (dpAttr?.GroupName is null) - { - var categoryAttr = provider.GetAttribute(); - return categoryAttr is null ? defaultValue ?? provider.GetTypeName() : categoryAttr.Category; - } + /// + /// Retrieves the category name for the given attribute provider using DisplayAttribute or CategoryAttribute. + /// + /// The custom attribute provider. + /// The default category if none is found. + /// The category name. + public static string GetCategory(this ICustomAttributeProvider provider, string defaultValue = null) + { + var dpAttr = provider.GetAttribute(); - return dpAttr.GetGroupName(); + if (dpAttr?.GroupName is null) + { + var categoryAttr = provider.GetAttribute(); + return categoryAttr is null ? defaultValue ?? provider.GetTypeName() : categoryAttr.Category; } - /// - /// Retrieves the type name from the member information of the custom attribute provider. - /// - /// The custom attribute provider. - /// The name of the type. - private static string GetTypeName(this ICustomAttributeProvider provider) + return dpAttr.GetGroupName(); + } + + /// + /// Retrieves the type name from the member information of the custom attribute provider. + /// + /// The custom attribute provider. + /// The name of the type. + private static string GetTypeName(this ICustomAttributeProvider provider) + { + return ((MemberInfo)provider).Name; + } + + /// + /// Retrieves the display name of the given object. + /// If the object is an Enum, its field display name is returned; otherwise, it uses custom attribute providers or type name. + /// + /// The object for which to get the display name. + /// The display name. + public static string GetDisplayName(this object value) + { + if (value is null) + throw new ArgumentNullException(nameof(value)); + + if (value is not Enum) { - return ((MemberInfo)provider).Name; + if (value is ICustomAttributeProvider provider) + return provider.GetDisplayName(); + + return value.GetType().GetDisplayName(value.ToString()); } - /// - /// Retrieves the display name of the given object. - /// If the object is an Enum, its field display name is returned; otherwise, it uses custom attribute providers or type name. - /// - /// The object for which to get the display name. - /// The display name. - public static string GetDisplayName(this object value) - { - if (value is null) - throw new ArgumentNullException(nameof(value)); + return value.GetFieldDisplayName(); + } - if (value is not Enum) - { - if (value is ICustomAttributeProvider provider) - return provider.GetDisplayName(); + /// + /// Internal helper method to get a value from an enum field using the provided functions. + /// + private static TValue Get(object field, Func func, Func getDefault, Func aggregate, bool canSplit = true) + { + if (field is null) throw new ArgumentNullException(nameof(field)); + if (func is null) throw new ArgumentNullException(nameof(func)); + if (getDefault is null) throw new ArgumentNullException(nameof(getDefault)); + if (aggregate is null) throw new ArgumentNullException(nameof(aggregate)); - return value.GetType().GetDisplayName(value.ToString()); - } + if (field is not Enum) + throw new ArgumentException($"{field}", nameof(field)); - return value.GetFieldDisplayName(); - } + var type = field.GetType(); - /// - /// Internal helper method to get a value from an enum field using the provided functions. - /// - private static TValue Get(object field, Func func, Func getDefault, Func aggregate, bool canSplit = true) + if (canSplit && type.IsFlags()) { - if (field is null) throw new ArgumentNullException(nameof(field)); - if (func is null) throw new ArgumentNullException(nameof(func)); - if (getDefault is null) throw new ArgumentNullException(nameof(getDefault)); - if (aggregate is null) throw new ArgumentNullException(nameof(aggregate)); + var parts = field.SplitMask2().Where(f => f.To() != default).ToArray(); - if (field is not Enum) - throw new ArgumentException($"{field}", nameof(field)); + if (parts.Length > 1) + return parts.Select(p => Get(p, func, getDefault, (_, _) => throw new NotSupportedException(), false)).Aggregate(aggregate); + } - var type = field.GetType(); + var fi = type.GetField(field.ToString()); - if (canSplit && type.IsFlags()) - { - var parts = field.SplitMask2().Where(f => f.To() != default).ToArray(); + // Bit mask value or external constant handling. + if (fi is null) + return getDefault(field); - if (parts.Length > 1) - return parts.Select(p => Get(p, func, getDefault, (_, _) => throw new NotSupportedException(), false)).Aggregate(aggregate); - } - - var fi = type.GetField(field.ToString()); + return func(fi); + } - // Bit mask value or external constant handling. - if (fi is null) - return getDefault(field); + /// + /// Retrieves the display name for an enum field using its custom attributes. + /// + /// The enum field. + /// The display name associated with the field. + public static string GetFieldDisplayName(this object field) + => Get(field, fi => fi.GetDisplayName(), f => f.ToString(), (s1, s2) => s1.IsEmpty() ? s2 : (s2.IsEmpty() ? s1 : $"{s1}, {s2}")); - return func(fi); - } + /// + /// Retrieves the description for an enum field using the DisplayAttribute or DescriptionAttribute. + /// + /// The enum field. + /// The field's description or an empty string if none is provided. + public static string GetFieldDescription(this object field) + => Get(field, fi => fi.GetAttribute()?.GetDescription(), f => null, (s1, s2) => s1.IsEmpty() ? s2 : (s2.IsEmpty() ? s1 : $"{s1}, {s2}")) ?? string.Empty; - /// - /// Retrieves the display name for an enum field using its custom attributes. - /// - /// The enum field. - /// The display name associated with the field. - public static string GetFieldDisplayName(this object field) - => Get(field, fi => fi.GetDisplayName(), f => f.ToString(), (s1, s2) => s1.IsEmpty() ? s2 : (s2.IsEmpty() ? s1 : $"{s1}, {s2}")); - - /// - /// Retrieves the description for an enum field using the DisplayAttribute or DescriptionAttribute. - /// - /// The enum field. - /// The field's description or an empty string if none is provided. - public static string GetFieldDescription(this object field) - => Get(field, fi => fi.GetAttribute()?.GetDescription(), f => null, (s1, s2) => s1.IsEmpty() ? s2 : (s2.IsEmpty() ? s1 : $"{s1}, {s2}")) ?? string.Empty; - - /// - /// Retrieves the icon URI for an enum field based on its IconAttribute. - /// - /// The enum field. - /// The icon URI if available; otherwise, null. - public static Uri GetFieldIcon(this object field) - => Get(field, fi => - { - var attr = fi.GetAttribute(); - - return - attr is null ? null : - attr.IsFullPath ? new Uri(attr.Icon, UriKind.Relative) : attr.Icon.GetResourceUrl(fi.ReflectedType); - }, f => null, (s1, s2) => s1); - - /// - /// Retrieves the documentation URL for a type based on its DocAttribute. - /// - /// The type to retrieve the doc URL for. - /// The documentation URL if available; otherwise, null. - public static string GetDocUrl(this Type type) - => type.GetAttribute()?.DocUrl; - - /// - /// Retrieves the icon URL for a type based on its IconAttribute. - /// - /// The type to retrieve the icon URL for. - /// The icon URI if available; otherwise, null. - public static Uri GetIconUrl(this Type type) + /// + /// Retrieves the icon URI for an enum field based on its IconAttribute. + /// + /// The enum field. + /// The icon URI if available; otherwise, null. + public static Uri GetFieldIcon(this object field) + => Get(field, fi => { - var attr = type.GetAttribute(); - return attr is null ? null : (attr.IsFullPath ? new Uri(attr.Icon, UriKind.Relative) : attr.Icon.GetResourceUrl(type)); - } + var attr = fi.GetAttribute(); - /// - /// Retrieves the resource URL for the given resource name using the entry assembly. - /// - /// The resource name. - /// The resource URI. - public static Uri GetResourceUrl(this string resName) - { - return Assembly.GetEntryAssembly().GetResourceUrl(resName); - } + return + attr is null ? null : + attr.IsFullPath ? new Uri(attr.Icon, UriKind.Relative) : attr.Icon.GetResourceUrl(fi.ReflectedType); + }, f => null, (s1, s2) => s1); - /// - /// Retrieves the resource URL for the given resource name using the specified type's assembly. - /// - /// The resource name. - /// The type whose assembly is used to obtain the resource. - /// The resource URI. - public static Uri GetResourceUrl(this string resName, Type type) - { - if (type is null) - throw new ArgumentNullException(nameof(type)); + /// + /// Retrieves the documentation URL for a type based on its DocAttribute. + /// + /// The type to retrieve the doc URL for. + /// The documentation URL if available; otherwise, null. + public static string GetDocUrl(this Type type) + => type.GetAttribute()?.DocUrl; - return type.Assembly.GetResourceUrl(resName); - } + /// + /// Retrieves the icon URL for a type based on its IconAttribute. + /// + /// The type to retrieve the icon URL for. + /// The icon URI if available; otherwise, null. + public static Uri GetIconUrl(this Type type) + { + var attr = type.GetAttribute(); + return attr is null ? null : (attr.IsFullPath ? new Uri(attr.Icon, UriKind.Relative) : attr.Icon.GetResourceUrl(type)); + } - /// - /// Internal helper to build a resource URL for a given assembly and resource name. - /// - /// The assembly containing the resource. - /// The name of the resource. - /// The constructed resource URI. - private static Uri GetResourceUrl(this Assembly assembly, string resName) - { - if (assembly is null) - throw new ArgumentNullException(nameof(assembly)); + /// + /// Retrieves the resource URL for the given resource name using the entry assembly. + /// + /// The resource name. + /// The resource URI. + public static Uri GetResourceUrl(this string resName) + { + return Assembly.GetEntryAssembly().GetResourceUrl(resName); + } - if (resName.IsEmpty()) - throw new ArgumentNullException(nameof(resName)); + /// + /// Retrieves the resource URL for the given resource name using the specified type's assembly. + /// + /// The resource name. + /// The type whose assembly is used to obtain the resource. + /// The resource URI. + public static Uri GetResourceUrl(this string resName, Type type) + { + if (type is null) + throw new ArgumentNullException(nameof(type)); - var name = assembly.FullName; - return new Uri($"/{name.Substring(0, name.IndexOf(','))};component/" + resName, UriKind.Relative); - } + return type.Assembly.GetResourceUrl(resName); + } - /// - /// Retrieves the items source items from an ItemsSourceAttribute instance. - /// - /// The items source attribute. - /// An enumerable of IItemsSourceItem. - public static IEnumerable GetValues(this ItemsSourceAttribute attr) - { - if (attr is null) - throw new ArgumentNullException(nameof(attr)); + /// + /// Internal helper to build a resource URL for a given assembly and resource name. + /// + /// The assembly containing the resource. + /// The name of the resource. + /// The constructed resource URI. + private static Uri GetResourceUrl(this Assembly assembly, string resName) + { + if (assembly is null) + throw new ArgumentNullException(nameof(assembly)); - return attr.Type.CreateInstance().Values; - } + if (resName.IsEmpty()) + throw new ArgumentNullException(nameof(resName)); - /// - /// Determines if the server credentials contain sufficient data for auto login. - /// - /// The server credentials. - /// True if auto login is possible; otherwise, false. - public static bool CanAutoLogin(this ServerCredentials credentials) - { - if (credentials is null) - throw new ArgumentNullException(nameof(credentials)); + var name = assembly.FullName; + return new Uri($"/{name.Substring(0, name.IndexOf(','))};component/" + resName, UriKind.Relative); + } - return !credentials.Token.IsEmpty() || (!credentials.Email.IsEmptyOrWhiteSpace() && !credentials.Password.IsEmpty()); - } + /// + /// Retrieves the items source items from an ItemsSourceAttribute instance. + /// + /// The items source attribute. + /// An enumerable of IItemsSourceItem. + public static IEnumerable GetValues(this ItemsSourceAttribute attr) + { + if (attr is null) + throw new ArgumentNullException(nameof(attr)); - /// - /// Attempts to retrieve the GUID associated with the specified control type using its GuidAttribute. - /// - /// The control type. - /// The GUID if available; otherwise, null. - public static Guid? TryGetGuid(this Type controlType) - { - var guidAttr = controlType.GetAttribute(); - return guidAttr is null ? null : controlType.GUID; - } + return attr.Type.CreateInstance().Values; + } + + /// + /// Determines if the server credentials contain sufficient data for auto login. + /// + /// The server credentials. + /// True if auto login is possible; otherwise, false. + public static bool CanAutoLogin(this ServerCredentials credentials) + { + if (credentials is null) + throw new ArgumentNullException(nameof(credentials)); + + return !credentials.Token.IsEmpty() || (!credentials.Email.IsEmptyOrWhiteSpace() && !credentials.Password.IsEmpty()); + } + + /// + /// Attempts to retrieve the GUID associated with the specified control type using its GuidAttribute. + /// + /// The control type. + /// The GUID if available; otherwise, null. + public static Guid? TryGetGuid(this Type controlType) + { + var guidAttr = controlType.GetAttribute(); + return guidAttr is null ? null : controlType.GUID; + } + + /// + /// Converts the Guid to its 32-digit hexadecimal ("N") format. + /// + /// The Guid value. + /// The "N" formatted string. + public static string ToN(this Guid id) + => id.ToString("N"); - /// - /// Converts the Guid to its 32-digit hexadecimal ("N") format. - /// - /// The Guid value. - /// The "N" formatted string. - public static string ToN(this Guid id) - => id.ToString("N"); - - /// - /// Casts a PropertyDescriptorCollection to an enumerable of PropertyDescriptor. - /// - /// The collection of property descriptors. - /// An enumerable of PropertyDescriptor. - public static IEnumerable Typed(this PropertyDescriptorCollection col) - => col.Cast(); - - /// - /// Retrieves basic properties (marked with BasicSettingAttribute) for the given instance, optionally retrieving nested properties recursively. - /// - /// The object instance. - /// The maximum recursion depth. - /// An enumerable of tuples containing the property descriptor and its path. - public static IEnumerable<(PropertyDescriptor prop, string path)> GetBasicProperties(this object instance, int maxDepth = 0) + /// + /// Casts a PropertyDescriptorCollection to an enumerable of PropertyDescriptor. + /// + /// The collection of property descriptors. + /// An enumerable of PropertyDescriptor. + public static IEnumerable Typed(this PropertyDescriptorCollection col) + => col.Cast(); + + /// + /// Retrieves basic properties (marked with BasicSettingAttribute) for the given instance, optionally retrieving nested properties recursively. + /// + /// The object instance. + /// The maximum recursion depth. + /// An enumerable of tuples containing the property descriptor and its path. + public static IEnumerable<(PropertyDescriptor prop, string path)> GetBasicProperties(this object instance, int maxDepth = 0) + { + static IEnumerable<(PropertyDescriptor, string)> getRecursive(object instance, int maxDepth, string prefix) { - static IEnumerable<(PropertyDescriptor, string)> getRecursive(object instance, int maxDepth, string prefix) - { - if (instance is null) - throw new ArgumentNullException(nameof(instance)); + if (instance is null) + throw new ArgumentNullException(nameof(instance)); - if (maxDepth < 0) - throw new ArgumentOutOfRangeException(nameof(maxDepth), maxDepth, "Invalid value.".Localize()); + if (maxDepth < 0) + throw new ArgumentOutOfRangeException(nameof(maxDepth), maxDepth, "Invalid value.".Localize()); - var properties = TypeDescriptor.GetProperties(instance, [new BasicSettingAttribute()]).Typed(); + var properties = TypeDescriptor.GetProperties(instance, [new BasicSettingAttribute()]).Typed(); - foreach (var property in properties) - { - var fullPath = $"{prefix}{property.Name}"; + foreach (var property in properties) + { + var fullPath = $"{prefix}{property.Name}"; - var hasNested = false; + var hasNested = false; - if (maxDepth > 0 && - !property.PropertyType.IsSerializablePrimitive() && - property.GetValue(instance) is object nestedInstance) + if (maxDepth > 0 && + !property.PropertyType.IsSerializablePrimitive() && + property.GetValue(instance) is object nestedInstance) + { + foreach (var nestedProperty in getRecursive(nestedInstance, maxDepth - 1, $"{fullPath}.")) { - foreach (var nestedProperty in getRecursive(nestedInstance, maxDepth - 1, $"{fullPath}.")) - { - hasNested = true; - yield return nestedProperty; - } + hasNested = true; + yield return nestedProperty; } - - if (!hasNested) - yield return (property, fullPath); } - } - return getRecursive(instance, maxDepth, string.Empty); + if (!hasNested) + yield return (property, fullPath); + } } - /// - /// Retrieves the filtered property descriptors from a custom type descriptor based on the provided attributes. - /// - /// The custom type descriptor. - /// The attributes to filter properties. - /// A filtered PropertyDescriptorCollection. - public static PropertyDescriptorCollection GetFilteredProperties(this ICustomTypeDescriptor descriptor, Attribute[] attributes) - { - var allProperties = descriptor.GetProperties(); + return getRecursive(instance, maxDepth, string.Empty); + } - if (attributes == null || attributes.Length == 0) - return allProperties; + /// + /// Retrieves the filtered property descriptors from a custom type descriptor based on the provided attributes. + /// + /// The custom type descriptor. + /// The attributes to filter properties. + /// A filtered PropertyDescriptorCollection. + public static PropertyDescriptorCollection GetFilteredProperties(this ICustomTypeDescriptor descriptor, Attribute[] attributes) + { + var allProperties = descriptor.GetProperties(); + + if (attributes == null || attributes.Length == 0) + return allProperties; - return new([.. allProperties.Typed().Where(p => attributes.All(attr => + return new([.. allProperties.Typed().Where(p => attributes.All(attr => + { + var propAttr = p.Attributes[attr.GetType()]; + + if (propAttr is null) { - var propAttr = p.Attributes[attr.GetType()]; + if (attr.GetType().GetField("Default", BindingFlags.Static | BindingFlags.Public)?.GetValue(null) is not Attribute defaultAttr) + return false; - if (propAttr is null) - { - if (attr.GetType().GetField("Default", BindingFlags.Static | BindingFlags.Public)?.GetValue(null) is not Attribute defaultAttr) - return false; + propAttr = defaultAttr; + } - propAttr = defaultAttr; - } + return attr.Match(propAttr); + }))]); + } - return attr.Match(propAttr); - }))]); - } + /// + /// Attempts to retrieve the default property descriptor from a collection based on the DefaultPropertyAttribute. + /// + /// The property descriptor collection. + /// The type to check for default property. + /// The default PropertyDescriptor if found; otherwise, null. + public static PropertyDescriptor TryGetDefault(this PropertyDescriptorCollection properties, Type type) + { + var attr = type.GetAttribute(); - /// - /// Attempts to retrieve the default property descriptor from a collection based on the DefaultPropertyAttribute. - /// - /// The property descriptor collection. - /// The type to check for default property. - /// The default PropertyDescriptor if found; otherwise, null. - public static PropertyDescriptor TryGetDefault(this PropertyDescriptorCollection properties, Type type) - { - var attr = type.GetAttribute(); + if (attr != null) + return properties.Find(attr.Name, ignoreCase: true); - if (attr != null) - return properties.Find(attr.Name, ignoreCase: true); + return null; + } - return null; - } + /// + /// Attempts to retrieve the default event descriptor from a collection based on the DefaultEventAttribute. + /// + /// The event descriptor collection. + /// The type to check for default event. + /// The default EventDescriptor if found; otherwise, null. + public static EventDescriptor TryGetDefault(this EventDescriptorCollection events, Type type) + { + var attr = type.GetAttribute(); - /// - /// Attempts to retrieve the default event descriptor from a collection based on the DefaultEventAttribute. - /// - /// The event descriptor collection. - /// The type to check for default event. - /// The default EventDescriptor if found; otherwise, null. - public static EventDescriptor TryGetDefault(this EventDescriptorCollection events, Type type) - { - var attr = type.GetAttribute(); + if (attr != null) + return events.Find(attr.Name, ignoreCase: true); - if (attr != null) - return events.Find(attr.Name, ignoreCase: true); + return null; + } - return null; - } + /// + /// Sets the ExpandableObjectConverter attribute for the entity. + /// + /// The type of the attribute entity. + /// The attribute entity. + /// True to set as expandable; otherwise, false. + /// The updated attribute entity. + public static TEntity SetExpandable(this TEntity entity, bool expandable) + where TEntity : IAttributesEntity + => SetAttribute(entity, expandable, () => new TypeConverterAttribute(typeof(ExpandableObjectConverter))); - /// - /// Sets the ExpandableObjectConverter attribute for the entity. - /// - /// The type of the attribute entity. - /// The attribute entity. - /// True to set as expandable; otherwise, false. - /// The updated attribute entity. - public static TEntity SetExpandable(this TEntity entity, bool expandable) - where TEntity : IAttributesEntity - => SetAttribute(entity, expandable, () => new TypeConverterAttribute(typeof(ExpandableObjectConverter))); - - /// - /// Sets a custom editor attribute for the entity. - /// - /// The type of the attribute entity. - /// The attribute entity. - /// The editor attribute to set. - /// The updated attribute entity. - public static TEntity SetEditor(this TEntity entity, Attribute editor) - where TEntity : IAttributesEntity - { - if (editor == null) - throw new ArgumentNullException(nameof(editor)); + /// + /// Sets a custom editor attribute for the entity. + /// + /// The type of the attribute entity. + /// The attribute entity. + /// The editor attribute to set. + /// The updated attribute entity. + public static TEntity SetEditor(this TEntity entity, Attribute editor) + where TEntity : IAttributesEntity + { + if (editor == null) + throw new ArgumentNullException(nameof(editor)); - return SetAttribute(entity, true, () => editor); - } + return SetAttribute(entity, true, () => editor); + } - /// - /// Sets the DisplayAttribute for the entity with specified group, display name, description, and order. - /// - /// The type of the attribute entity. - /// The attribute entity. - /// The group/category name. - /// The display name. - /// The description text. - /// The display order. - /// The updated attribute entity. - public static TEntity SetDisplay(this TEntity entity, string groupName, string displayName, string description, int order) - where TEntity : IAttributesEntity - => SetAttribute(entity, true, () => new DisplayAttribute - { - Name = displayName, - Description = description, - GroupName = groupName, - Order = order, - }); - - /// - /// Sets the ReadOnlyAttribute for the entity. - /// - /// The type of the attribute entity. - /// The attribute entity. - /// True to mark as read-only; otherwise, false. - /// The updated attribute entity. - public static TEntity SetReadOnly(this TEntity entity, bool readOnly = true) - where TEntity : IAttributesEntity - => SetAttribute(entity, readOnly, () => new ReadOnlyAttribute(true)); - - /// - /// Sets the BasicSettingAttribute for the entity. - /// - /// The type of the attribute entity. - /// The attribute entity. - /// True to mark as basic; otherwise, false. - /// The updated attribute entity. - public static TEntity SetBasic(this TEntity entity, bool isBasic = true) - where TEntity : IAttributesEntity - => SetAttribute(entity, isBasic, () => new BasicSettingAttribute()); - - /// - /// Sets the BrowsableAttribute (non-browsable) for the entity. - /// - /// The type of the attribute entity. - /// The attribute entity. - /// True to mark as non-browsable; otherwise, false. - /// The updated attribute entity. - public static TEntity SetNonBrowsable(this TEntity entity, bool nonBrowsable = true) - where TEntity : IAttributesEntity - => SetAttribute(entity, nonBrowsable, () => new BrowsableAttribute(false)); - - /// - /// Sets or removes an attribute of type TAttribute for the entity based on the provided value. - /// - /// The type of the attribute entity. - /// The type of attribute to set. - /// The attribute entity. - /// True to add the attribute; false to remove it. - /// A function to create the attribute instance. - /// The updated attribute entity. - public static TEntity SetAttribute(this TEntity entity, bool value, Func create) - where TEntity : IAttributesEntity - where TAttribute : Attribute + /// + /// Sets the DisplayAttribute for the entity with specified group, display name, description, and order. + /// + /// The type of the attribute entity. + /// The attribute entity. + /// The group/category name. + /// The display name. + /// The description text. + /// The display order. + /// The updated attribute entity. + public static TEntity SetDisplay(this TEntity entity, string groupName, string displayName, string description, int order) + where TEntity : IAttributesEntity + => SetAttribute(entity, true, () => new DisplayAttribute { - if (create is null) - throw new ArgumentNullException(nameof(create)); + Name = displayName, + Description = description, + GroupName = groupName, + Order = order, + }); - var attrs = entity.Attributes; + /// + /// Sets the ReadOnlyAttribute for the entity. + /// + /// The type of the attribute entity. + /// The attribute entity. + /// True to mark as read-only; otherwise, false. + /// The updated attribute entity. + public static TEntity SetReadOnly(this TEntity entity, bool readOnly = true) + where TEntity : IAttributesEntity + => SetAttribute(entity, readOnly, () => new ReadOnlyAttribute(true)); - if (typeof(TAttribute) == typeof(Attribute)) - { - var attr = create(); - var type = attr.GetType(); - attrs.RemoveWhere(a => a.GetType().Is(type)); + /// + /// Sets the BasicSettingAttribute for the entity. + /// + /// The type of the attribute entity. + /// The attribute entity. + /// True to mark as basic; otherwise, false. + /// The updated attribute entity. + public static TEntity SetBasic(this TEntity entity, bool isBasic = true) + where TEntity : IAttributesEntity + => SetAttribute(entity, isBasic, () => new BasicSettingAttribute()); - if (value) - attrs.Add(attr); - } - else - { - attrs.RemoveWhere(a => a is TAttribute); + /// + /// Sets the BrowsableAttribute (non-browsable) for the entity. + /// + /// The type of the attribute entity. + /// The attribute entity. + /// True to mark as non-browsable; otherwise, false. + /// The updated attribute entity. + public static TEntity SetNonBrowsable(this TEntity entity, bool nonBrowsable = true) + where TEntity : IAttributesEntity + => SetAttribute(entity, nonBrowsable, () => new BrowsableAttribute(false)); - if (value) - attrs.Add(create()); - } + /// + /// Sets or removes an attribute of type TAttribute for the entity based on the provided value. + /// + /// The type of the attribute entity. + /// The type of attribute to set. + /// The attribute entity. + /// True to add the attribute; false to remove it. + /// A function to create the attribute instance. + /// The updated attribute entity. + public static TEntity SetAttribute(this TEntity entity, bool value, Func create) + where TEntity : IAttributesEntity + where TAttribute : Attribute + { + if (create is null) + throw new ArgumentNullException(nameof(create)); - return entity; - } + var attrs = entity.Attributes; - /// - /// Checks if the entity has a BasicSettingAttribute. - /// - /// The type of the attribute entity. - /// The attribute entity. - /// True if the entity is marked as basic; otherwise, false. - public static bool IsBasic(this TEntity entity) - where TEntity : IAttributesEntity - => IsAny(entity, (BasicSettingAttribute a) => true); - - /// - /// Checks if the entity is marked as read-only via a ReadOnlyAttribute. - /// - /// The type of the attribute entity. - /// The attribute entity. - /// True if the entity is read-only; otherwise, false. - public static bool IsReadOnly(this TEntity entity) - where TEntity : IAttributesEntity - => IsAny(entity, (ReadOnlyAttribute a) => a.IsReadOnly); - - /// - /// Checks if all BrowsableAttributes on the entity indicate it is browsable. - /// - /// The type of the attribute entity. - /// The attribute entity. - /// True if the entity is browsable; otherwise, false. - public static bool IsBrowsable(this TEntity entity) - where TEntity : IAttributesEntity - => IsAll(entity, (BrowsableAttribute a) => a.Browsable); - - /// - /// Retrieves the display name for the entity from its DisplayAttribute. - /// - /// The type of the attribute entity. - /// The attribute entity. - /// The display name if set; otherwise, null. - public static string GetDisplayName(this TEntity entity) - where TEntity : IAttributesEntity - => entity.GetDisplay()?.Name; - - /// - /// Retrieves the description for the entity from its DisplayAttribute. - /// - /// The type of the attribute entity. - /// The attribute entity. - /// The description if set; otherwise, null. - public static string GetDescription(this TEntity entity) - where TEntity : IAttributesEntity - => entity.GetDisplay()?.Description; - - /// - /// Retrieves the group name for the entity from its DisplayAttribute. - /// - /// The type of the attribute entity. - /// The attribute entity. - /// The group name if set; otherwise, null. - public static string GetGroupName(this TEntity entity) - where TEntity : IAttributesEntity - => entity.GetDisplay()?.GroupName; - - /// - /// Retrieves the DisplayAttribute from the entity. - /// - /// The type of the attribute entity. - /// The attribute entity. - /// The DisplayAttribute if exists; otherwise, null. - public static DisplayAttribute GetDisplay(this TEntity entity) - where TEntity : IAttributesEntity - => entity.Attributes.OfType().FirstOrDefault(); - - /// - /// Helper method to determine if any attribute of type TAttribute on the entity satisfies the condition. - /// - private static bool IsAny(this TEntity entity, Func condition) - where TEntity : IAttributesEntity - => Attrs(entity).Any(condition); - - /// - /// Helper method to determine if all attributes of type TAttribute on the entity satisfy the condition. - /// - private static bool IsAll(this TEntity entity, Func condition) - where TEntity : IAttributesEntity - => Attrs(entity).All(condition); - - /// - /// Retrieves all attributes of the specified type TAttribute from the entity. - /// - private static IEnumerable Attrs(this TEntity entity) - where TEntity : IAttributesEntity - => entity.Attributes.OfType(); - - /// - /// Sets a RequiredAttribute as a validator for the entity. - /// - /// The type of the attribute entity. - /// The attribute entity. - /// The updated attribute entity. - public static TEntity SetRequired(this TEntity entity) - where TEntity : IAttributesEntity - => entity.SetValidator(new RequiredAttribute()); - - /// - /// Adds a custom validation attribute as a validator for the entity. - /// - /// The type of the attribute entity. - /// The attribute entity. - /// The validation attribute to add. - /// The updated attribute entity. - public static TEntity SetValidator(this TEntity entity, ValidationAttribute validator) - where TEntity : IAttributesEntity + if (typeof(TAttribute) == typeof(Attribute)) { - entity.Attributes.Add(validator); - return entity; - } + var attr = create(); + var type = attr.GetType(); + attrs.RemoveWhere(a => a.GetType().Is(type)); - /// - /// Validates the given value against all ValidationAttribute validators on the entity. - /// - /// The type of the attribute entity. - /// The attribute entity. - /// The value to validate. - /// True if all validators deem the value valid; otherwise, false. - public static bool IsValid(this TEntity entity, object value) - where TEntity : IAttributesEntity - => entity.Attrs().All(v => v.IsValid(value)); - - /// - /// Determines whether the debugger is currently waiting for input or output. - /// - /// The debugger instance. - /// True if the debugger is waiting; otherwise, false. - public static bool IsWaiting(this IDebugger debugger) + if (value) + attrs.Add(attr); + } + else { - if (debugger is null) - throw new ArgumentNullException(nameof(debugger)); + attrs.RemoveWhere(a => a is TAttribute); - return debugger.IsWaitingOnInput || debugger.IsWaitingOnOutput; + if (value) + attrs.Add(create()); } + + return entity; + } + + /// + /// Checks if the entity has a BasicSettingAttribute. + /// + /// The type of the attribute entity. + /// The attribute entity. + /// True if the entity is marked as basic; otherwise, false. + public static bool IsBasic(this TEntity entity) + where TEntity : IAttributesEntity + => IsAny(entity, (BasicSettingAttribute a) => true); + + /// + /// Checks if the entity is marked as read-only via a ReadOnlyAttribute. + /// + /// The type of the attribute entity. + /// The attribute entity. + /// True if the entity is read-only; otherwise, false. + public static bool IsReadOnly(this TEntity entity) + where TEntity : IAttributesEntity + => IsAny(entity, (ReadOnlyAttribute a) => a.IsReadOnly); + + /// + /// Checks if all BrowsableAttributes on the entity indicate it is browsable. + /// + /// The type of the attribute entity. + /// The attribute entity. + /// True if the entity is browsable; otherwise, false. + public static bool IsBrowsable(this TEntity entity) + where TEntity : IAttributesEntity + => IsAll(entity, (BrowsableAttribute a) => a.Browsable); + + /// + /// Retrieves the display name for the entity from its DisplayAttribute. + /// + /// The type of the attribute entity. + /// The attribute entity. + /// The display name if set; otherwise, null. + public static string GetDisplayName(this TEntity entity) + where TEntity : IAttributesEntity + => entity.GetDisplay()?.Name; + + /// + /// Retrieves the description for the entity from its DisplayAttribute. + /// + /// The type of the attribute entity. + /// The attribute entity. + /// The description if set; otherwise, null. + public static string GetDescription(this TEntity entity) + where TEntity : IAttributesEntity + => entity.GetDisplay()?.Description; + + /// + /// Retrieves the group name for the entity from its DisplayAttribute. + /// + /// The type of the attribute entity. + /// The attribute entity. + /// The group name if set; otherwise, null. + public static string GetGroupName(this TEntity entity) + where TEntity : IAttributesEntity + => entity.GetDisplay()?.GroupName; + + /// + /// Retrieves the DisplayAttribute from the entity. + /// + /// The type of the attribute entity. + /// The attribute entity. + /// The DisplayAttribute if exists; otherwise, null. + public static DisplayAttribute GetDisplay(this TEntity entity) + where TEntity : IAttributesEntity + => entity.Attributes.OfType().FirstOrDefault(); + + /// + /// Helper method to determine if any attribute of type TAttribute on the entity satisfies the condition. + /// + private static bool IsAny(this TEntity entity, Func condition) + where TEntity : IAttributesEntity + => Attrs(entity).Any(condition); + + /// + /// Helper method to determine if all attributes of type TAttribute on the entity satisfy the condition. + /// + private static bool IsAll(this TEntity entity, Func condition) + where TEntity : IAttributesEntity + => Attrs(entity).All(condition); + + /// + /// Retrieves all attributes of the specified type TAttribute from the entity. + /// + private static IEnumerable Attrs(this TEntity entity) + where TEntity : IAttributesEntity + => entity.Attributes.OfType(); + + /// + /// Sets a RequiredAttribute as a validator for the entity. + /// + /// The type of the attribute entity. + /// The attribute entity. + /// The updated attribute entity. + public static TEntity SetRequired(this TEntity entity) + where TEntity : IAttributesEntity + => entity.SetValidator(new RequiredAttribute()); + + /// + /// Adds a custom validation attribute as a validator for the entity. + /// + /// The type of the attribute entity. + /// The attribute entity. + /// The validation attribute to add. + /// The updated attribute entity. + public static TEntity SetValidator(this TEntity entity, ValidationAttribute validator) + where TEntity : IAttributesEntity + { + entity.Attributes.Add(validator); + return entity; + } + + /// + /// Validates the given value against all ValidationAttribute validators on the entity. + /// + /// The type of the attribute entity. + /// The attribute entity. + /// The value to validate. + /// True if all validators deem the value valid; otherwise, false. + public static bool IsValid(this TEntity entity, object value) + where TEntity : IAttributesEntity + => entity.Attrs().All(v => v.IsValid(value)); + + /// + /// Determines whether the debugger is currently waiting for input or output. + /// + /// The debugger instance. + /// True if the debugger is waiting; otherwise, false. + public static bool IsWaiting(this IDebugger debugger) + { + if (debugger is null) + throw new ArgumentNullException(nameof(debugger)); + + return debugger.IsWaitingOnInput || debugger.IsWaitingOnOutput; } } \ No newline at end of file diff --git a/ComponentModel/FileSizeFormatProvider.cs b/ComponentModel/FileSizeFormatProvider.cs index 643e0679..a1951468 100644 --- a/ComponentModel/FileSizeFormatProvider.cs +++ b/ComponentModel/FileSizeFormatProvider.cs @@ -1,95 +1,94 @@ -namespace Ecng.ComponentModel +namespace Ecng.ComponentModel; + +#region Using Directives + +using System; + +using Ecng.Common; + +#endregion + +/// +/// File size format provider. +/// +public class FileSizeFormatProvider : IFormatProvider, ICustomFormatter { - #region Using Directives + #region Private Fields - using System; + private const string _fileSizeFormat = "fs"; - using Ecng.Common; + private static readonly string[] _letters = ["b", "kb", "mb", "gb", "tb", "pb"]; #endregion + #region IFormatProvider Members + /// - /// File size format provider. + /// Gets an object that provides formatting services for the specified type. /// - public class FileSizeFormatProvider : IFormatProvider, ICustomFormatter + /// An object that specifies the type of format object to get. + /// + /// The current instance, if formatType is the same type as the current instance; otherwise, null. + /// + object IFormatProvider.GetFormat(Type formatType) { - #region Private Fields - - private const string _fileSizeFormat = "fs"; - - private static readonly string[] _letters = ["b", "kb", "mb", "gb", "tb", "pb"]; + return formatType.Is() ? this : null; + } - #endregion + #endregion - #region IFormatProvider Members + #region ICustomFormatter Members - /// - /// Gets an object that provides formatting services for the specified type. - /// - /// An object that specifies the type of format object to get. - /// - /// The current instance, if formatType is the same type as the current instance; otherwise, null. - /// - object IFormatProvider.GetFormat(Type formatType) + /// + /// Converts the value of a specified object to an equivalent string representation using specified format and culture-specific formatting information. + /// + /// A format string containing formatting specifications. + /// An object to format. + /// An object that supplies format information about the current instance. + /// + /// The string representation of the value of arg, formatted as specified by format and formatProvider. + /// + string ICustomFormatter.Format(string format, object arg, IFormatProvider formatProvider) + { + if (format is null || !format.StartsWith(_fileSizeFormat)) { - return formatType.Is() ? this : null; + return DefaultFormat(format, arg, formatProvider); } - #endregion + decimal size; - #region ICustomFormatter Members + try + { + size = arg.To(); + } + catch (InvalidCastException) + { + return DefaultFormat(format, arg, formatProvider); + } - /// - /// Converts the value of a specified object to an equivalent string representation using specified format and culture-specific formatting information. - /// - /// A format string containing formatting specifications. - /// An object to format. - /// An object that supplies format information about the current instance. - /// - /// The string representation of the value of arg, formatted as specified by format and formatProvider. - /// - string ICustomFormatter.Format(string format, object arg, IFormatProvider formatProvider) + byte i = 0; + while ((size >= FileSizes.KB) && (i < _letters.Length - 1)) { - if (format is null || !format.StartsWith(_fileSizeFormat)) - { - return DefaultFormat(format, arg, formatProvider); - } - - decimal size; - - try - { - size = arg.To(); - } - catch (InvalidCastException) - { - return DefaultFormat(format, arg, formatProvider); - } - - byte i = 0; - while ((size >= FileSizes.KB) && (i < _letters.Length - 1)) - { - i++; - size /= FileSizes.KB; - } - - var precision = format.Substring(2); - - if (precision.IsEmpty()) - precision = "2"; - - return ("{0:N" + precision + "}{1}").Put(size, _letters[i]); + i++; + size /= FileSizes.KB; } - #endregion + var precision = format.Substring(2); - #region DefaultFormat + if (precision.IsEmpty()) + precision = "2"; - private static string DefaultFormat(string format, object arg, IFormatProvider formatProvider) - { - return arg is IFormattable formattableArg ? formattableArg.ToString(format, formatProvider) : arg.ToString(); - } + return ("{0:N" + precision + "}{1}").Put(size, _letters[i]); + } - #endregion + #endregion + + #region DefaultFormat + + private static string DefaultFormat(string format, object arg, IFormatProvider formatProvider) + { + return arg is IFormattable formattableArg ? formattableArg.ToString(format, formatProvider) : arg.ToString(); } + + #endregion } \ No newline at end of file diff --git a/ComponentModel/IDispatcher.cs b/ComponentModel/IDispatcher.cs index 07605908..a4504432 100644 --- a/ComponentModel/IDispatcher.cs +++ b/ComponentModel/IDispatcher.cs @@ -1,39 +1,38 @@ -namespace Ecng.ComponentModel -{ - using System; - using System.Threading.Tasks; +namespace Ecng.ComponentModel; + +using System; +using System.Threading.Tasks; +/// +/// Threads dispatcher. +/// +public interface IDispatcher +{ /// - /// Threads dispatcher. + /// Call action in dispatcher thread. /// - public interface IDispatcher - { - /// - /// Call action in dispatcher thread. - /// - /// Action. - void Invoke(Action action); - - /// - /// Call action in dispatcher thread. - /// - /// Action. - void InvokeAsync(Action action); + /// Action. + void Invoke(Action action); - /// - /// Verify that current thread is dispatcher thread. - /// - /// Operation result. - bool CheckAccess(); - } + /// + /// Call action in dispatcher thread. + /// + /// Action. + void InvokeAsync(Action action); /// - /// Dummy dispatcher. + /// Verify that current thread is dispatcher thread. /// - public class DummyDispatcher : IDispatcher - { - bool IDispatcher.CheckAccess() => true; - void IDispatcher.Invoke(Action action) => action(); - void IDispatcher.InvokeAsync(Action action) => Task.Run(action); - } + /// Operation result. + bool CheckAccess(); +} + +/// +/// Dummy dispatcher. +/// +public class DummyDispatcher : IDispatcher +{ + bool IDispatcher.CheckAccess() => true; + void IDispatcher.Invoke(Action action) => action(); + void IDispatcher.InvokeAsync(Action action) => Task.Run(action); } \ No newline at end of file diff --git a/ComponentModel/IFileBrowserEditor.cs b/ComponentModel/IFileBrowserEditor.cs index d71220d0..1c72ff0e 100644 --- a/ComponentModel/IFileBrowserEditor.cs +++ b/ComponentModel/IFileBrowserEditor.cs @@ -1,9 +1,8 @@ -namespace Ecng.ComponentModel +namespace Ecng.ComponentModel; + +/// +/// Defines methods for performing file browser editing operations. +/// +public interface IFileBrowserEditor { - /// - /// Defines methods for performing file browser editing operations. - /// - public interface IFileBrowserEditor - { - } } \ No newline at end of file diff --git a/ComponentModel/IFileSaveEditor.cs b/ComponentModel/IFileSaveEditor.cs index 15a1b39e..c76655d4 100644 --- a/ComponentModel/IFileSaveEditor.cs +++ b/ComponentModel/IFileSaveEditor.cs @@ -1,9 +1,8 @@ -namespace Ecng.ComponentModel +namespace Ecng.ComponentModel; + +/// +/// Provides an interface for file save editor components. +/// +public interface IFileSaveEditor { - /// - /// Provides an interface for file save editor components. - /// - public interface IFileSaveEditor - { - } } \ No newline at end of file diff --git a/ComponentModel/IFolderBrowserEditor.cs b/ComponentModel/IFolderBrowserEditor.cs index 985ccd66..07615523 100644 --- a/ComponentModel/IFolderBrowserEditor.cs +++ b/ComponentModel/IFolderBrowserEditor.cs @@ -1,9 +1,8 @@ -namespace Ecng.ComponentModel +namespace Ecng.ComponentModel; + +/// +/// Provides an interface for implementing folder browser editing functionality. +/// +public interface IFolderBrowserEditor { - /// - /// Provides an interface for implementing folder browser editing functionality. - /// - public interface IFolderBrowserEditor - { - } } \ No newline at end of file diff --git a/ComponentModel/IItemsSource.cs b/ComponentModel/IItemsSource.cs index 37620f90..20e9b68d 100644 --- a/ComponentModel/IItemsSource.cs +++ b/ComponentModel/IItemsSource.cs @@ -1,542 +1,541 @@ -namespace Ecng.ComponentModel +namespace Ecng.ComponentModel; + +using System; +using System.Linq; +using System.ComponentModel; +using System.Collections.Generic; +using System.Collections; +using System.Reflection; + +using Ecng.Common; + +/// +/// Represents an item used as a source for UI components, with properties for display, description, icon, and state. +/// +public interface IItemsSourceItem : INotifyPropertyChangedEx { - using System; - using System.Linq; - using System.ComponentModel; - using System.Collections.Generic; - using System.Collections; - using System.Reflection; + /// + /// Gets the display name of the item. + /// + string DisplayName { get; } - using Ecng.Common; + /// + /// Gets the description of the item. + /// + string Description { get; } /// - /// Represents an item used as a source for UI components, with properties for display, description, icon, and state. + /// Gets the URI of the icon representing the item. /// - public interface IItemsSourceItem : INotifyPropertyChangedEx - { - /// - /// Gets the display name of the item. - /// - string DisplayName { get; } - - /// - /// Gets the description of the item. - /// - string Description { get; } - - /// - /// Gets the URI of the icon representing the item. - /// - Uri Icon { get; } - - /// - /// Gets a value indicating whether the item is marked as obsolete. - /// - bool IsObsolete { get; } - - /// - /// Gets the underlying value of the item. - /// - object Value { get; } - } + Uri Icon { get; } /// - /// Represents an item source item with a strongly typed value. + /// Gets a value indicating whether the item is marked as obsolete. /// - /// The type of the value of the item. - public interface IItemsSourceItem : IItemsSourceItem - { - /// - /// Gets the strongly typed value of the item. - /// - new TValue Value { get; } - } + bool IsObsolete { get; } /// - /// Represents an item source item with a strongly typed value. Provides functionality for property change notifications. + /// Gets the underlying value of the item. /// - /// The type of the value. - /// The value of the item. - /// Function to get the display name of the item. - /// Function to get the description of the item. - /// The URI of the icon representing the item. - /// A value indicating whether the item is obsolete. - public class ItemsSourceItem(T value, Func getDisplayName, Func getDescription, Uri iconUri, bool isObsolete) : NotifiableObject, IItemsSourceItem - { - private readonly Func _getDisplayName = getDisplayName ?? throw new ArgumentNullException(nameof(getDisplayName)); - private readonly Func _getDescription = getDescription ?? throw new ArgumentNullException(nameof(getDescription)); - - object IItemsSourceItem.Value => Value; - - /// - /// Gets the strongly typed value of the item. - /// - public T Value { get; } = value; - - /// - /// Gets the display name of the item. - /// - public string DisplayName => _getDisplayName(); - - /// - /// Gets the description of the item. - /// - public string Description => _getDescription(); - - /// - /// Gets the icon URI of the item. - /// - public Uri Icon { get; } = iconUri; - - /// - /// Gets a value indicating whether the item is marked as obsolete. - /// - public bool IsObsolete { get; } = isObsolete; - - /// - /// Returns the display name of the item. - /// - /// The display name. - public override string ToString() => DisplayName; - } + object Value { get; } +} + +/// +/// Represents an item source item with a strongly typed value. +/// +/// The type of the value of the item. +public interface IItemsSourceItem : IItemsSourceItem +{ + /// + /// Gets the strongly typed value of the item. + /// + new TValue Value { get; } +} + +/// +/// Represents an item source item with a strongly typed value. Provides functionality for property change notifications. +/// +/// The type of the value. +/// The value of the item. +/// Function to get the display name of the item. +/// Function to get the description of the item. +/// The URI of the icon representing the item. +/// A value indicating whether the item is obsolete. +public class ItemsSourceItem(T value, Func getDisplayName, Func getDescription, Uri iconUri, bool isObsolete) : NotifiableObject, IItemsSourceItem +{ + private readonly Func _getDisplayName = getDisplayName ?? throw new ArgumentNullException(nameof(getDisplayName)); + private readonly Func _getDescription = getDescription ?? throw new ArgumentNullException(nameof(getDescription)); + + object IItemsSourceItem.Value => Value; /// - /// Represents a source of items. + /// Gets the strongly typed value of the item. /// - public interface IItemsSource - { - /// - /// Gets the collection of items. - /// - IEnumerable Values { get; } - - /// - /// Gets the type of the value represented by the items. - /// - Type ValueType { get; } - - /// - /// Gets a value indicating whether obsolete items should be excluded. - /// - bool ExcludeObsolete { get; } - - /// - /// Gets the sort order for the items. - /// - ListSortDirection? SortOrder { get; } - - /// - /// Creates a new item using the specified value. - /// - /// The value for creating the new item. - /// The newly created item. - IItemsSourceItem CreateNewItem(object value); - } + public T Value { get; } = value; /// - /// Represents a strongly typed source of items. + /// Gets the display name of the item. /// - /// The type of the value represented in the items. - public interface IItemsSource : IItemsSource - { - /// - /// Gets the collection of strongly typed items. - /// - new IEnumerable> Values { get; } - - /// - /// Creates a new strongly typed item using the specified value. - /// - /// The value for creating the new item. - /// The newly created strongly typed item. - IItemsSourceItem CreateNewItem(TValue value); - } + public string DisplayName => _getDisplayName(); + + /// + /// Gets the description of the item. + /// + public string Description => _getDescription(); + + /// + /// Gets the icon URI of the item. + /// + public Uri Icon { get; } = iconUri; + + /// + /// Gets a value indicating whether the item is marked as obsolete. + /// + public bool IsObsolete { get; } = isObsolete; + + /// + /// Returns the display name of the item. + /// + /// The display name. + public override string ToString() => DisplayName; +} + +/// +/// Represents a source of items. +/// +public interface IItemsSource +{ + /// + /// Gets the collection of items. + /// + IEnumerable Values { get; } + + /// + /// Gets the type of the value represented by the items. + /// + Type ValueType { get; } + + /// + /// Gets a value indicating whether obsolete items should be excluded. + /// + bool ExcludeObsolete { get; } + + /// + /// Gets the sort order for the items. + /// + ListSortDirection? SortOrder { get; } + + /// + /// Creates a new item using the specified value. + /// + /// The value for creating the new item. + /// The newly created item. + IItemsSourceItem CreateNewItem(object value); +} + +/// +/// Represents a strongly typed source of items. +/// +/// The type of the value represented in the items. +public interface IItemsSource : IItemsSource +{ + /// + /// Gets the collection of strongly typed items. + /// + new IEnumerable> Values { get; } + + /// + /// Creates a new strongly typed item using the specified value. + /// + /// The value for creating the new item. + /// The newly created strongly typed item. + IItemsSourceItem CreateNewItem(TValue value); +} + +/// +/// Represents the base implementation for an items source containing values of type . +/// +/// The type of the values in the source. +public class ItemsSourceBase : IItemsSource +{ + private readonly T[] _values; + private readonly Lazy>> _items; + private readonly Func _filter; + + private readonly Func _getName; + private readonly Func _getDescription; + + /// + /// Gets a value indicating whether obsolete items should be excluded. + /// + public bool ExcludeObsolete { get; } + + /// + /// Gets the sort order for sorting the items. + /// + public ListSortDirection? SortOrder { get; } /// - /// Represents the base implementation for an items source containing values of type . + /// Gets the collection of items. /// - /// The type of the values in the source. - public class ItemsSourceBase : IItemsSource + IEnumerable IItemsSource.Values => Values; + + /// + /// Gets the collection of strongly typed items. + /// + public IEnumerable> Values => _items.Value; + + /// + /// Gets the type of the value. + /// + public virtual Type ValueType => typeof(T); + + /// + /// Initializes a new instance of the class using the specified values. + /// + /// The collection of values or items. + /// A value indicating whether obsolete items should be excluded. + /// The sort order for the items. + /// A filter function to apply on items. + /// A function to retrieve the display name from a value. + /// A function to retrieve the description from a value. + public ItemsSourceBase(IEnumerable values, bool excludeObsolete, ListSortDirection? sortOrder, Func filter, Func getName, Func getDescription) { - private readonly T[] _values; - private readonly Lazy>> _items; - private readonly Func _filter; - - private readonly Func _getName; - private readonly Func _getDescription; - - /// - /// Gets a value indicating whether obsolete items should be excluded. - /// - public bool ExcludeObsolete { get; } - - /// - /// Gets the sort order for sorting the items. - /// - public ListSortDirection? SortOrder { get; } - - /// - /// Gets the collection of items. - /// - IEnumerable IItemsSource.Values => Values; - - /// - /// Gets the collection of strongly typed items. - /// - public IEnumerable> Values => _items.Value; - - /// - /// Gets the type of the value. - /// - public virtual Type ValueType => typeof(T); - - /// - /// Initializes a new instance of the class using the specified values. - /// - /// The collection of values or items. - /// A value indicating whether obsolete items should be excluded. - /// The sort order for the items. - /// A filter function to apply on items. - /// A function to retrieve the display name from a value. - /// A function to retrieve the description from a value. - public ItemsSourceBase(IEnumerable values, bool excludeObsolete, ListSortDirection? sortOrder, Func filter, Func getName, Func getDescription) + SortOrder = sortOrder; + ExcludeObsolete = excludeObsolete; + _filter = filter; + _getName = getName; + _getDescription = getDescription; + + var objects = values?.Cast().ToArray(); + if (objects != null) { - SortOrder = sortOrder; - ExcludeObsolete = excludeObsolete; - _filter = filter; - _getName = getName; - _getDescription = getDescription; - - var objects = values?.Cast().ToArray(); - if (objects != null) + if (objects.All(o => o is T)) { - if (objects.All(o => o is T)) - { - _values = [.. objects.Cast()]; - _items = new Lazy>>(() => CreateItems(GetValues())); - } - else if (objects.All(o => o is IItemsSourceItem)) - { - var itemsArr = objects.Cast>().ToArray(); - _values = [.. itemsArr.Select(item => item.Value)]; - _items = new Lazy>>(() => FilterItems(itemsArr)); - } - else if (objects.All(o => o is IItemsSourceItem iisi && iisi.Value is T)) - { - var itemsArr = objects.Cast().Select(CreateNewItem).ToArray(); - _values = [.. itemsArr.Select(item => item.Value)]; - _items = new Lazy>>(() => FilterItems(itemsArr)); - } - else - { - throw new ArgumentException($"{nameof(values)} is expected to contain either {typeof(T).Name} or {nameof(IItemsSourceItem)}<{typeof(T).Name}> items (mix not supported). actual types found: {objects.Select(o => o.GetType().Name).Distinct().Join(",")}"); - } + _values = [.. objects.Cast()]; + _items = new Lazy>>(() => CreateItems(GetValues())); + } + else if (objects.All(o => o is IItemsSourceItem)) + { + var itemsArr = objects.Cast>().ToArray(); + _values = [.. itemsArr.Select(item => item.Value)]; + _items = new Lazy>>(() => FilterItems(itemsArr)); + } + else if (objects.All(o => o is IItemsSourceItem iisi && iisi.Value is T)) + { + var itemsArr = objects.Cast().Select(CreateNewItem).ToArray(); + _values = [.. itemsArr.Select(item => item.Value)]; + _items = new Lazy>>(() => FilterItems(itemsArr)); } else { - if (typeof(T).IsEnum) - _values = [.. Enumerator.GetValues()]; - - _items = new Lazy>>(() => CreateItems(GetValues())); + throw new ArgumentException($"{nameof(values)} is expected to contain either {typeof(T).Name} or {nameof(IItemsSourceItem)}<{typeof(T).Name}> items (mix not supported). actual types found: {objects.Select(o => o.GetType().Name).Distinct().Join(",")}"); } } - - /// - /// Initializes a new instance of the class. - /// - /// A value indicating whether obsolete items should be excluded. - /// The sort order for the items. - /// A filter function to apply on items. - /// A function to retrieve the display name from a value. - /// A function to retrieve the description from a value. - public ItemsSourceBase(bool excludeObsolete, ListSortDirection? sortOrder = null, Func filter = null, Func getName = null, Func getDescription = null) - : this(null, excludeObsolete, sortOrder, filter, getName, getDescription) { } - - /// - /// Initializes a new instance of the class using a collection of values. - /// - /// The collection of values or items. - /// A function to retrieve the display name from a value. - /// A function to retrieve the description from a value. - public ItemsSourceBase(IEnumerable values, Func getName = null, Func getDescription = null) - : this(values, true, null, null, getName, getDescription) { } - - /// - /// Initializes a new instance of the class. - /// - public ItemsSourceBase() : this(null) { } - - /// - /// Gets the format string for displaying the value. Override to provide a custom format. - /// - protected virtual string Format => null; - - /// - /// Retrieves the display name for the specified value. - /// - /// The value whose display name is to be retrieved. - /// The display name. - protected virtual string GetName(T value) + else { - if (_getName != null) - return _getName(value); + if (typeof(T).IsEnum) + _values = [.. Enumerator.GetValues()]; - var f = Format; - return f.IsEmptyOrWhiteSpace() ? value.GetDisplayName() : string.Format($"{{0:{f}}}", value); + _items = new Lazy>>(() => CreateItems(GetValues())); } + } - /// - /// Retrieves the description for the specified value. - /// - /// The value whose description is to be retrieved. - /// The description. - protected virtual string GetDescription(T value) => _getDescription is null ? (typeof(T).IsEnum ? value.GetFieldDescription() : null) : _getDescription(value); - - /// - /// Retrieves the icon for the specified value. - /// - /// The value whose icon is to be retrieved. - /// The icon URI. - protected virtual Uri GetIcon(T value) => typeof(T).IsEnum ? value.GetFieldIcon() : null; - - /// - /// Determines whether the specified value is marked as obsolete. - /// - /// The value to check. - /// true if obsolete; otherwise, false. - protected virtual bool GetIsObsolete(T value) => typeof(T).IsEnum && value.GetAttributeOfType() != null; - - /// - /// Applies filtering to the given item. - /// - /// The item to filter. - /// true if the item should be included; otherwise, false. - protected virtual bool Filter(IItemsSourceItem item) - => (!ExcludeObsolete || !item.IsObsolete) && _filter?.Invoke(item) != false; - - IItemsSourceItem IItemsSource.CreateNewItem(object value) - { - if (value is not T typedVal) - throw new ArgumentException(nameof(value)); + /// + /// Initializes a new instance of the class. + /// + /// A value indicating whether obsolete items should be excluded. + /// The sort order for the items. + /// A filter function to apply on items. + /// A function to retrieve the display name from a value. + /// A function to retrieve the description from a value. + public ItemsSourceBase(bool excludeObsolete, ListSortDirection? sortOrder = null, Func filter = null, Func getName = null, Func getDescription = null) + : this(null, excludeObsolete, sortOrder, filter, getName, getDescription) { } - return CreateNewItem(typedVal); - } + /// + /// Initializes a new instance of the class using a collection of values. + /// + /// The collection of values or items. + /// A function to retrieve the display name from a value. + /// A function to retrieve the description from a value. + public ItemsSourceBase(IEnumerable values, Func getName = null, Func getDescription = null) + : this(values, true, null, null, getName, getDescription) { } - private IItemsSourceItem CreateNewItem(IItemsSourceItem fromItem) - { - return new ItemsSourceItem( - (T)fromItem.Value, - () => fromItem.DisplayName, - () => fromItem.Description, - fromItem.Icon, - fromItem.IsObsolete - ); - } + /// + /// Initializes a new instance of the class. + /// + public ItemsSourceBase() : this(null) { } - /// - /// Creates a new item with the specified value. - /// - /// The value for the new item. - /// The newly created item. - public virtual IItemsSourceItem CreateNewItem(T value) - { - return new ItemsSourceItem( - value, - () => GetName(value), - () => GetDescription(value), - GetIcon(value), - GetIsObsolete(value) - ); - } + /// + /// Gets the format string for displaying the value. Override to provide a custom format. + /// + protected virtual string Format => null; - /// - /// Retrieves the underlying collection of values. - /// - /// The collection of values. - protected virtual IEnumerable GetValues() => _values; + /// + /// Retrieves the display name for the specified value. + /// + /// The value whose display name is to be retrieved. + /// The display name. + protected virtual string GetName(T value) + { + if (_getName != null) + return _getName(value); - private IEnumerable> FilterItems(IEnumerable> items) - { - items ??= []; + var f = Format; + return f.IsEmptyOrWhiteSpace() ? value.GetDisplayName() : string.Format($"{{0:{f}}}", value); + } - items = items.Where(Filter); + /// + /// Retrieves the description for the specified value. + /// + /// The value whose description is to be retrieved. + /// The description. + protected virtual string GetDescription(T value) => _getDescription is null ? (typeof(T).IsEnum ? value.GetFieldDescription() : null) : _getDescription(value); - if (SortOrder != null) - items = SortOrder == ListSortDirection.Ascending ? - items.OrderBy(item => item.DisplayName, StringComparer.CurrentCultureIgnoreCase) : - items.OrderByDescending(item => item.DisplayName, StringComparer.CurrentCultureIgnoreCase); + /// + /// Retrieves the icon for the specified value. + /// + /// The value whose icon is to be retrieved. + /// The icon URI. + protected virtual Uri GetIcon(T value) => typeof(T).IsEnum ? value.GetFieldIcon() : null; - return [.. items]; - } + /// + /// Determines whether the specified value is marked as obsolete. + /// + /// The value to check. + /// true if obsolete; otherwise, false. + protected virtual bool GetIsObsolete(T value) => typeof(T).IsEnum && value.GetAttributeOfType() != null; + + /// + /// Applies filtering to the given item. + /// + /// The item to filter. + /// true if the item should be included; otherwise, false. + protected virtual bool Filter(IItemsSourceItem item) + => (!ExcludeObsolete || !item.IsObsolete) && _filter?.Invoke(item) != false; - private IEnumerable> CreateItems(IEnumerable values) => FilterItems(values?.Select(CreateNewItem)); + IItemsSourceItem IItemsSource.CreateNewItem(object value) + { + if (value is not T typedVal) + throw new ArgumentException(nameof(value)); + + return CreateNewItem(typedVal); + } + + private IItemsSourceItem CreateNewItem(IItemsSourceItem fromItem) + { + return new ItemsSourceItem( + (T)fromItem.Value, + () => fromItem.DisplayName, + () => fromItem.Description, + fromItem.Icon, + fromItem.IsObsolete + ); } /// - /// Represents a non-generic items source based on objects. + /// Creates a new item with the specified value. /// - public class ItemsSourceBase : ItemsSourceBase + /// The value for the new item. + /// The newly created item. + public virtual IItemsSourceItem CreateNewItem(T value) { - private static IItemsSource Create(IEnumerable values, Type itemValueType, bool? excludeObsolete, ListSortDirection? sortOrder, Func filter, Func getName, Func getDescription) - { - itemValueType ??= GetSourceValueType(values); + return new ItemsSourceItem( + value, + () => GetName(value), + () => GetDescription(value), + GetIcon(value), + GetIsObsolete(value) + ); + } - var srcType = typeof(ItemsSourceBase<>).Make(itemValueType); + /// + /// Retrieves the underlying collection of values. + /// + /// The collection of values. + protected virtual IEnumerable GetValues() => _values; - excludeObsolete ??= true; + private IEnumerable> FilterItems(IEnumerable> items) + { + items ??= []; - return (IItemsSource)Activator.CreateInstance( - srcType, - BindingFlags.Instance | BindingFlags.CreateInstance | BindingFlags.Public | BindingFlags.NonPublic, - null, - [values, excludeObsolete.Value, sortOrder, filter, getName, getDescription], - null, - null); - } + items = items.Where(Filter); - /// - /// Creates an from an object. - /// - /// The source value. - /// The expected type of the items. - /// A value indicating whether obsolete items should be excluded. - /// The sort order for the items. - /// A filter function to apply on items. - /// A function to retrieve the display name from a value. - /// A function to retrieve the description from a value. - /// An instance of . - public static IItemsSource Create(object val, Type itemValueType, bool? excludeObsolete = null, ListSortDirection? sortOrder = null, Func filter = null, Func getName = null, Func getDescription = null) - { - switch (val) - { - case null: - itemValueType ??= typeof(object); - return Create(itemValueType.CreateArray(0), itemValueType, excludeObsolete, sortOrder, filter, getName, getDescription); + if (SortOrder != null) + items = SortOrder == ListSortDirection.Ascending ? + items.OrderBy(item => item.DisplayName, StringComparer.CurrentCultureIgnoreCase) : + items.OrderByDescending(item => item.DisplayName, StringComparer.CurrentCultureIgnoreCase); - case IItemsSource src: - if ((itemValueType is null || src.ValueType == itemValueType) && (excludeObsolete is null || excludeObsolete == src.ExcludeObsolete) && (sortOrder is null || sortOrder == src.SortOrder) && filter is null) - return src; + return [.. items]; + } - return Create(src.Values, itemValueType, excludeObsolete, sortOrder, filter, getName, getDescription); + private IEnumerable> CreateItems(IEnumerable values) => FilterItems(values?.Select(CreateNewItem)); +} - case IEnumerable ie: - return Create(ie, itemValueType, excludeObsolete, sortOrder, filter, getName, getDescription); +/// +/// Represents a non-generic items source based on objects. +/// +public class ItemsSourceBase : ItemsSourceBase +{ + private static IItemsSource Create(IEnumerable values, Type itemValueType, bool? excludeObsolete, ListSortDirection? sortOrder, Func filter, Func getName, Func getDescription) + { + itemValueType ??= GetSourceValueType(values); - default: - throw new ArgumentException($"cannot create {typeof(IItemsSource).FullName} from '{val.GetType().FullName}'"); - } - } + var srcType = typeof(ItemsSourceBase<>).Make(itemValueType); - private static Type GetSourceValueType(IEnumerable values) - { - if (values is null) - throw new ArgumentNullException(nameof(values)); + excludeObsolete ??= true; - var itemType = GetParamType(values.GetType(), typeof(IEnumerable<>)); - var innerType = GetParamType(itemType, typeof(IItemsSourceItem<>)); + return (IItemsSource)Activator.CreateInstance( + srcType, + BindingFlags.Instance | BindingFlags.CreateInstance | BindingFlags.Public | BindingFlags.NonPublic, + null, + [values, excludeObsolete.Value, sortOrder, filter, getName, getDescription], + null, + null); + } - if (innerType != null && innerType != typeof(object)) - return innerType; + /// + /// Creates an from an object. + /// + /// The source value. + /// The expected type of the items. + /// A value indicating whether obsolete items should be excluded. + /// The sort order for the items. + /// A filter function to apply on items. + /// A function to retrieve the display name from a value. + /// A function to retrieve the description from a value. + /// An instance of . + public static IItemsSource Create(object val, Type itemValueType, bool? excludeObsolete = null, ListSortDirection? sortOrder = null, Func filter = null, Func getName = null, Func getDescription = null) + { + switch (val) + { + case null: + itemValueType ??= typeof(object); + return Create(itemValueType.CreateArray(0), itemValueType, excludeObsolete, sortOrder, filter, getName, getDescription); - if (itemType != null && !itemType.Is() && itemType != typeof(object)) - return itemType; + case IItemsSource src: + if ((itemValueType is null || src.ValueType == itemValueType) && (excludeObsolete is null || excludeObsolete == src.ExcludeObsolete) && (sortOrder is null || sortOrder == src.SortOrder) && filter is null) + return src; - bool foundItems, foundValues; - foundItems = foundValues = false; + return Create(src.Values, itemValueType, excludeObsolete, sortOrder, filter, getName, getDescription); - var types = values.Cast().Select(o => - { - var t = o.GetType(); - var innerItemType = GetParamType(t, typeof(IItemsSourceItem<>)); + case IEnumerable ie: + return Create(ie, itemValueType, excludeObsolete, sortOrder, filter, getName, getDescription); - if (innerItemType != null) - { - foundItems = true; - return innerItemType; - } + default: + throw new ArgumentException($"cannot create {typeof(IItemsSource).FullName} from '{val.GetType().FullName}'"); + } + } - if (o is IItemsSourceItem iisi) - { - foundItems = true; - return iisi.Value.GetType(); - } + private static Type GetSourceValueType(IEnumerable values) + { + if (values is null) + throw new ArgumentNullException(nameof(values)); - foundValues = true; - return t; - }).ToArray(); + var itemType = GetParamType(values.GetType(), typeof(IEnumerable<>)); + var innerType = GetParamType(itemType, typeof(IItemsSourceItem<>)); - if (foundItems && foundValues) - throw new ArgumentException($"{nameof(values)} contains elements of incompatible types"); + if (innerType != null && innerType != typeof(object)) + return innerType; - return GetCommonType(types); - } + if (itemType != null && !itemType.Is() && itemType != typeof(object)) + return itemType; - private static Type GetParamType(Type type, Type genericInterfaceType) - { - if (type is null) - return null; - - return new[] { type } - .Concat(type.GetInterfaces()) - .Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == genericInterfaceType) - .Select(i => i.GetGenericArguments()[0]) - .FirstOrDefault(); - } + bool foundItems, foundValues; + foundItems = foundValues = false; - private static Type GetCommonType(Type[] types) + var types = values.Cast().Select(o => { - if (types is null) - throw new ArgumentNullException(nameof(types)); - - if (types.Length == 0) - return typeof(object); + var t = o.GetType(); + var innerItemType = GetParamType(t, typeof(IItemsSourceItem<>)); - var type = types[0]; + if (innerItemType != null) + { + foundItems = true; + return innerItemType; + } - for (var i = 1; i < types.Length; ++i) + if (o is IItemsSourceItem iisi) { - if (type.Is(types[i])) - type = types[i]; - else - while (!types[i].Is(type)) - type = type.BaseType; + foundItems = true; + return iisi.Value.GetType(); } - return type; + foundValues = true; + return t; + }).ToArray(); + + if (foundItems && foundValues) + throw new ArgumentException($"{nameof(values)} contains elements of incompatible types"); + + return GetCommonType(types); + } + + private static Type GetParamType(Type type, Type genericInterfaceType) + { + if (type is null) + return null; + + return new[] { type } + .Concat(type.GetInterfaces()) + .Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == genericInterfaceType) + .Select(i => i.GetGenericArguments()[0]) + .FirstOrDefault(); + } + + private static Type GetCommonType(Type[] types) + { + if (types is null) + throw new ArgumentNullException(nameof(types)); + + if (types.Length == 0) + return typeof(object); + + var type = types[0]; + + for (var i = 1; i < types.Length; ++i) + { + if (type.Is(types[i])) + type = types[i]; + else + while (!types[i].Is(type)) + type = type.BaseType; } + + return type; } +} + +/// +/// Specifies the items source type to be used for a property. +/// +[AttributeUsage(AttributeTargets.Property)] +public class ItemsSourceAttribute : Attribute +{ + /// + /// Gets the type to use for the items source. + /// + public Type Type { get; } /// - /// Specifies the items source type to be used for a property. + /// Gets or sets a value indicating whether the user is allowed to edit the items. /// - [AttributeUsage(AttributeTargets.Property)] - public class ItemsSourceAttribute : Attribute + public bool IsEditable { get; set; } + + /// + /// Initializes a new instance of the class with the specified source type. + /// + /// The type to use as the items source. + public ItemsSourceAttribute(Type type) { - /// - /// Gets the type to use for the items source. - /// - public Type Type { get; } - - /// - /// Gets or sets a value indicating whether the user is allowed to edit the items. - /// - public bool IsEditable { get; set; } - - /// - /// Initializes a new instance of the class with the specified source type. - /// - /// The type to use as the items source. - public ItemsSourceAttribute(Type type) - { - if (type is null) - throw new ArgumentNullException(nameof(type)); - - Type = - type.Is() - ? type - : type.IsEnum - ? typeof(ItemsSourceBase<>).Make(type) - : throw new ArgumentException($"Type '{type}' must implement the '{typeof(IItemsSource)}' interface or be an enum.", nameof(type)); - } + if (type is null) + throw new ArgumentNullException(nameof(type)); + + Type = + type.Is() + ? type + : type.IsEnum + ? typeof(ItemsSourceBase<>).Make(type) + : throw new ArgumentException($"Type '{type}' must implement the '{typeof(IItemsSource)}' interface or be an enum.", nameof(type)); } } \ No newline at end of file diff --git a/ComponentModel/INotifyPropertiesChanged.cs b/ComponentModel/INotifyPropertiesChanged.cs index 6de738b8..9030c6c9 100644 --- a/ComponentModel/INotifyPropertiesChanged.cs +++ b/ComponentModel/INotifyPropertiesChanged.cs @@ -1,15 +1,14 @@ -namespace Ecng.ComponentModel -{ - using System; +namespace Ecng.ComponentModel; + +using System; +/// +/// The interface describing the type with a variable number of properties. +/// +public interface INotifyPropertiesChanged +{ /// - /// The interface describing the type with a variable number of properties. + /// The available properties change event. /// - public interface INotifyPropertiesChanged - { - /// - /// The available properties change event. - /// - event Action PropertiesChanged; - } + event Action PropertiesChanged; } \ No newline at end of file diff --git a/ComponentModel/INotifyPropertyChangedEx.cs b/ComponentModel/INotifyPropertyChangedEx.cs index 48de71da..2b207f67 100644 --- a/ComponentModel/INotifyPropertyChangedEx.cs +++ b/ComponentModel/INotifyPropertyChangedEx.cs @@ -1,41 +1,40 @@ -namespace Ecng.ComponentModel +namespace Ecng.ComponentModel; + +using System; +using System.ComponentModel; +using System.Runtime.CompilerServices; + +/// +/// Extended version . +/// +public interface INotifyPropertyChangedEx : INotifyPropertyChanged { - using System; - using System.ComponentModel; - using System.Runtime.CompilerServices; + /// + /// Raise event . + /// + /// Property name. + void NotifyPropertyChanged([CallerMemberName]string propertyName = null); +} +/// +/// Extension class for . +/// +public static class NotifyPropertyChangedExHelper +{ /// - /// Extended version . + /// Filter. /// - public interface INotifyPropertyChangedEx : INotifyPropertyChanged - { - /// - /// Raise event . - /// - /// Property name. - void NotifyPropertyChanged([CallerMemberName]string propertyName = null); - } + public static Func Filter { get; set; } /// - /// Extension class for . + /// Invoke . /// - public static class NotifyPropertyChangedExHelper + /// Notify based object. + /// Property name. + public static void Notify(this T entity, [CallerMemberName]string propertyName = null) + where T : INotifyPropertyChangedEx { - /// - /// Filter. - /// - public static Func Filter { get; set; } - - /// - /// Invoke . - /// - /// Notify based object. - /// Property name. - public static void Notify(this T entity, [CallerMemberName]string propertyName = null) - where T : INotifyPropertyChangedEx - { - if (null == Filter || Filter(entity, propertyName)) - entity.NotifyPropertyChanged(propertyName); - } + if (null == Filter || Filter(entity, propertyName)) + entity.NotifyPropertyChanged(propertyName); } } \ No newline at end of file diff --git a/ComponentModel/IconAttribute.cs b/ComponentModel/IconAttribute.cs index 5f3c9b57..ee80c93a 100644 --- a/ComponentModel/IconAttribute.cs +++ b/ComponentModel/IconAttribute.cs @@ -1,27 +1,26 @@ -namespace Ecng.ComponentModel -{ - using System; +namespace Ecng.ComponentModel; + +using System; - using Ecng.Common; +using Ecng.Common; +/// +/// Icon attribute. +/// +/// +/// Create . +/// +/// +/// +public class IconAttribute(string icon, bool isFullPath = false) : Attribute +{ /// - /// Icon attribute. + /// Icon url. /// - /// - /// Create . - /// - /// - /// - public class IconAttribute(string icon, bool isFullPath = false) : Attribute - { - /// - /// Icon url. - /// - public string Icon { get; } = icon.ThrowIfEmpty(nameof(icon)); + public string Icon { get; } = icon.ThrowIfEmpty(nameof(icon)); - /// - /// is full path. - /// - public bool IsFullPath { get; } = isFullPath; - } + /// + /// is full path. + /// + public bool IsFullPath { get; } = isFullPath; } \ No newline at end of file diff --git a/ComponentModel/NotifiableObject.cs b/ComponentModel/NotifiableObject.cs index 7df2a489..dbca8fee 100644 --- a/ComponentModel/NotifiableObject.cs +++ b/ComponentModel/NotifiableObject.cs @@ -1,55 +1,54 @@ -namespace Ecng.ComponentModel -{ - using System; - using System.ComponentModel; - using System.Runtime.Serialization; - using System.Runtime.CompilerServices; +namespace Ecng.ComponentModel; + +using System; +using System.ComponentModel; +using System.Runtime.Serialization; +using System.Runtime.CompilerServices; - using Ecng.Common; +using Ecng.Common; +/// +/// Provides base functionality for notifying property changes to registered observers. +/// Implements both and . +/// +[Serializable] +[DataContract] +public abstract class NotifiableObject : INotifyPropertyChangedEx, INotifyPropertyChanging +{ /// - /// Provides base functionality for notifying property changes to registered observers. - /// Implements both and . + /// Occurs when a property value has changed. /// - [Serializable] - [DataContract] - public abstract class NotifiableObject : INotifyPropertyChangedEx, INotifyPropertyChanging - { - /// - /// Occurs when a property value has changed. - /// - public event PropertyChangedEventHandler PropertyChanged; + public event PropertyChangedEventHandler PropertyChanged; - /// - /// Occurs when a property value is changing. - /// - public event PropertyChangingEventHandler PropertyChanging; + /// + /// Occurs when a property value is changing. + /// + public event PropertyChangingEventHandler PropertyChanging; - /// - /// Raises the event. - /// - /// The name of the property that changed. This value is optional and can be provided automatically by the compiler. - public void NotifyPropertyChanged([CallerMemberName]string propertyName = null) - { - PropertyChanged?.Invoke(this, propertyName); - } + /// + /// Raises the event. + /// + /// The name of the property that changed. This value is optional and can be provided automatically by the compiler. + public void NotifyPropertyChanged([CallerMemberName]string propertyName = null) + { + PropertyChanged?.Invoke(this, propertyName); + } - /// - /// Invokes the property change notification. - /// - /// The name of the property that changed. This value is optional and can be provided automatically by the compiler. - protected void NotifyChanged([CallerMemberName]string propertyName = null) - { - this.Notify(propertyName); - } + /// + /// Invokes the property change notification. + /// + /// The name of the property that changed. This value is optional and can be provided automatically by the compiler. + protected void NotifyChanged([CallerMemberName]string propertyName = null) + { + this.Notify(propertyName); + } - /// - /// Raises the event. - /// - /// The name of the property that is changing. This value is optional and can be provided automatically by the compiler. - protected void NotifyChanging([CallerMemberName]string propertyName = null) - { - PropertyChanging?.Invoke(this, propertyName); - } + /// + /// Raises the event. + /// + /// The name of the property that is changing. This value is optional and can be provided automatically by the compiler. + protected void NotifyChanging([CallerMemberName]string propertyName = null) + { + PropertyChanging?.Invoke(this, propertyName); } } \ No newline at end of file diff --git a/ComponentModel/ObservableCollectionEx.cs b/ComponentModel/ObservableCollectionEx.cs index 9a79db59..ae7e1641 100644 --- a/ComponentModel/ObservableCollectionEx.cs +++ b/ComponentModel/ObservableCollectionEx.cs @@ -1,370 +1,369 @@ -namespace Ecng.ComponentModel +namespace Ecng.ComponentModel; + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Linq; + +using Ecng.Collections; + +/// +/// Represents an observable collection with extended methods, including range operations. +/// +/// The type of items contained in the collection. +public class ObservableCollectionEx : IListEx, IList, INotifyCollectionChanged, INotifyPropertyChanged { - using System; - using System.Collections; - using System.Collections.Generic; - using System.Collections.Specialized; - using System.ComponentModel; - using System.Linq; - - using Ecng.Collections; + private readonly List _items = []; /// - /// Represents an observable collection with extended methods, including range operations. + /// Occurs when the collection changes. /// - /// The type of items contained in the collection. - public class ObservableCollectionEx : IListEx, IList, INotifyCollectionChanged, INotifyPropertyChanged - { - private readonly List _items = []; + public event NotifyCollectionChangedEventHandler CollectionChanged; - /// - /// Occurs when the collection changes. - /// - public event NotifyCollectionChangedEventHandler CollectionChanged; + /// + /// Occurs when a property value changes. + /// + public event PropertyChangedEventHandler PropertyChanged; - /// - /// Occurs when a property value changes. - /// - public event PropertyChangedEventHandler PropertyChanged; + private Action> _addedRange; - private Action> _addedRange; + /// + /// Occurs after a range of items has been added to the collection. + /// + public event Action> AddedRange + { + add => _addedRange += value; + remove => _addedRange -= value; + } - /// - /// Occurs after a range of items has been added to the collection. - /// - public event Action> AddedRange - { - add => _addedRange += value; - remove => _addedRange -= value; - } + private Action> _removedRange; - private Action> _removedRange; + /// + /// Occurs after a range of items has been removed from the collection. + /// + public event Action> RemovedRange + { + add => _removedRange += value; + remove => _removedRange -= value; + } - /// - /// Occurs after a range of items has been removed from the collection. - /// - public event Action> RemovedRange - { - add => _removedRange += value; - remove => _removedRange -= value; - } + /// + /// Adds a range of items to the collection. + /// + /// The items to add. + public virtual void AddRange(IEnumerable items) + { + var arr = items.ToArray(); - /// - /// Adds a range of items to the collection. - /// - /// The items to add. - public virtual void AddRange(IEnumerable items) - { - var arr = items.ToArray(); + if (arr.Length == 0) + return; - if (arr.Length == 0) - return; + var index = _items.Count; - var index = _items.Count; + _items.AddRange(arr); - _items.AddRange(arr); + OnCountPropertyChanged(); + OnIndexerPropertyChanged(); - OnCountPropertyChanged(); - OnIndexerPropertyChanged(); + OnCollectionChanged(NotifyCollectionChangedAction.Add, arr, index); + } - OnCollectionChanged(NotifyCollectionChangedAction.Add, arr, index); - } + /// + /// Removes a range of items from the collection. + /// + /// The items to remove. + public virtual void RemoveRange(IEnumerable items) + { + var arr = items.ToArray(); - /// - /// Removes a range of items from the collection. - /// - /// The items to remove. - public virtual void RemoveRange(IEnumerable items) + if (arr.Length > 10000 || arr.Length > Count * 0.1) { - var arr = items.ToArray(); - - if (arr.Length > 10000 || arr.Length > Count * 0.1) - { - var temp = new HashSet(_items); - temp.RemoveRange(arr); - - Clear(); - AddRange(temp); - } - else - arr.ForEach(i => Remove(i)); + var temp = new HashSet(_items); + temp.RemoveRange(arr); + + Clear(); + AddRange(temp); } + else + arr.ForEach(i => Remove(i)); + } - /// - /// Removes a specified range of items starting at a given index. - /// - /// The starting index. - /// The number of items to remove. - /// The number of removed items. - public virtual int RemoveRange(int index, int count) - { - var items = _items.GetRange(index, count).ToArray(); + /// + /// Removes a specified range of items starting at a given index. + /// + /// The starting index. + /// The number of items to remove. + /// The number of removed items. + public virtual int RemoveRange(int index, int count) + { + var items = _items.GetRange(index, count).ToArray(); - if (items.Length == 0) - return 0; + if (items.Length == 0) + return 0; - _items.RemoveRange(index, count); + _items.RemoveRange(index, count); - OnRemove(items, index); + OnRemove(items, index); - return items.Length; - } + return items.Length; + } - /// - /// Returns an enumerator that iterates through the collection. - /// - /// An enumerator for the collection. - public IEnumerator GetEnumerator() - { - return _items.GetEnumerator(); - } + /// + /// Returns an enumerator that iterates through the collection. + /// + /// An enumerator for the collection. + public IEnumerator GetEnumerator() + { + return _items.GetEnumerator(); + } - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } - /// - /// Adds an item to the collection. - /// - /// The item to add. - public virtual void Add(TItem item) - { - AddRange([item]); - } + /// + /// Adds an item to the collection. + /// + /// The item to add. + public virtual void Add(TItem item) + { + AddRange([item]); + } - /// - /// Removes the first occurrence of a specific item from the collection. - /// - /// The item to remove. - /// true if the item was successfully removed; otherwise, false. - public virtual bool Remove(TItem item) - { - var index = _items.IndexOf(item); + /// + /// Removes the first occurrence of a specific item from the collection. + /// + /// The item to remove. + /// true if the item was successfully removed; otherwise, false. + public virtual bool Remove(TItem item) + { + var index = _items.IndexOf(item); - if (index == -1) - return false; + if (index == -1) + return false; - _items.RemoveAt(index); + _items.RemoveAt(index); - OnRemove([item], index); - return true; - } + OnRemove([item], index); + return true; + } - int IList.Add(object value) - { - Add((TItem)value); - return Count - 1; - } + int IList.Add(object value) + { + Add((TItem)value); + return Count - 1; + } - bool IList.Contains(object value) - { - return Contains((TItem)value); - } + bool IList.Contains(object value) + { + return Contains((TItem)value); + } - /// - /// Removes all items from the collection. - /// - public virtual void Clear() - { - _items.Clear(); + /// + /// Removes all items from the collection. + /// + public virtual void Clear() + { + _items.Clear(); - OnCountPropertyChanged(); - OnIndexerPropertyChanged(); - OnCollectionReset(); - } + OnCountPropertyChanged(); + OnIndexerPropertyChanged(); + OnCollectionReset(); + } - int IList.IndexOf(object value) - { - return IndexOf((TItem)value); - } + int IList.IndexOf(object value) + { + return IndexOf((TItem)value); + } - void IList.Insert(int index, object value) - { - Insert(index, (TItem)value); - } + void IList.Insert(int index, object value) + { + Insert(index, (TItem)value); + } - void IList.Remove(object value) - { - Remove((TItem)value); - } + void IList.Remove(object value) + { + Remove((TItem)value); + } - /// - /// Determines whether the collection contains a specific value. - /// - /// The item to locate. - /// true if the item is found; otherwise, false. - public bool Contains(TItem item) - { - return _items.Contains(item); - } + /// + /// Determines whether the collection contains a specific value. + /// + /// The item to locate. + /// true if the item is found; otherwise, false. + public bool Contains(TItem item) + { + return _items.Contains(item); + } - /// - /// Copies the elements of the collection to an array, starting at a particular index. - /// - /// The destination array. - /// The zero-based index at which copying begins. - public void CopyTo(TItem[] array, int arrayIndex) - { - _items.CopyTo(array, arrayIndex); - } + /// + /// Copies the elements of the collection to an array, starting at a particular index. + /// + /// The destination array. + /// The zero-based index at which copying begins. + public void CopyTo(TItem[] array, int arrayIndex) + { + _items.CopyTo(array, arrayIndex); + } - void ICollection.CopyTo(Array array, int index) - { - if (array is not TItem[] items) - items = [.. array.Cast()]; + void ICollection.CopyTo(Array array, int index) + { + if (array is not TItem[] items) + items = [.. array.Cast()]; - CopyTo(items, index); - } + CopyTo(items, index); + } - /// - /// Gets the number of elements contained in the collection. - /// - public int Count => _items.Count; + /// + /// Gets the number of elements contained in the collection. + /// + public int Count => _items.Count; - object ICollection.SyncRoot => this; + object ICollection.SyncRoot => this; - bool ICollection.IsSynchronized => true; + bool ICollection.IsSynchronized => true; - /// - /// Gets a value indicating whether the collection is read-only. - /// - public bool IsReadOnly => false; + /// + /// Gets a value indicating whether the collection is read-only. + /// + public bool IsReadOnly => false; - bool IList.IsFixedSize => false; + bool IList.IsFixedSize => false; - /// - /// Determines the index of a specific item in the collection. - /// - /// The item to locate. - /// The index of the item if found; otherwise, -1. - public int IndexOf(TItem item) - { - return _items.IndexOf(item); - } + /// + /// Determines the index of a specific item in the collection. + /// + /// The item to locate. + /// The index of the item if found; otherwise, -1. + public int IndexOf(TItem item) + { + return _items.IndexOf(item); + } - /// - /// Inserts an item into the collection at the specified index. - /// - /// The index at which the item should be inserted. - /// The item to insert. - public void Insert(int index, TItem item) - { - _items.Insert(index, item); + /// + /// Inserts an item into the collection at the specified index. + /// + /// The index at which the item should be inserted. + /// The item to insert. + public void Insert(int index, TItem item) + { + _items.Insert(index, item); - OnCountPropertyChanged(); - OnIndexerPropertyChanged(); + OnCountPropertyChanged(); + OnIndexerPropertyChanged(); - OnCollectionChanged(NotifyCollectionChangedAction.Add, [item], index); - } + OnCollectionChanged(NotifyCollectionChangedAction.Add, [item], index); + } - /// - /// Removes the item at the specified index of the collection. - /// - /// The zero-based index of the item to remove. - public void RemoveAt(int index) - { - var item = _items[index]; - _items.RemoveAt(index); + /// + /// Removes the item at the specified index of the collection. + /// + /// The zero-based index of the item to remove. + public void RemoveAt(int index) + { + var item = _items[index]; + _items.RemoveAt(index); - OnRemove([item], index); - } + OnRemove([item], index); + } - object IList.this[int index] - { - get => this[index]; - set => this[index] = (TItem)value; - } + object IList.this[int index] + { + get => this[index]; + set => this[index] = (TItem)value; + } - /// - /// Gets or sets the element at the specified index. - /// - /// The zero-based index of the element to get or set. - /// The element at the specified index. - public TItem this[int index] + /// + /// Gets or sets the element at the specified index. + /// + /// The zero-based index of the element to get or set. + /// The element at the specified index. + public TItem this[int index] + { + get => _items[index]; + set { - get => _items[index]; - set - { - var originalItem = _items[index]; - _items[index] = value; - - OnIndexerPropertyChanged(); - OnCollectionChanged(NotifyCollectionChangedAction.Replace, originalItem, value, index); - } - } + var originalItem = _items[index]; + _items[index] = value; - /// - /// Raises the collection changed event for a removal. - /// - /// The items removed. - /// The index from which the items were removed. - private void OnRemove(IList items, int index) - { - OnCountPropertyChanged(); OnIndexerPropertyChanged(); - - OnCollectionChanged(NotifyCollectionChangedAction.Remove, items, index); + OnCollectionChanged(NotifyCollectionChangedAction.Replace, originalItem, value, index); } + } - /// - /// Raises the PropertyChanged event. - /// - /// The name of the property that changed. - protected void OnPropertyChanged(string propertyName) - { - var evt = PropertyChanged; - evt?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } + /// + /// Raises the collection changed event for a removal. + /// + /// The items removed. + /// The index from which the items were removed. + private void OnRemove(IList items, int index) + { + OnCountPropertyChanged(); + OnIndexerPropertyChanged(); + + OnCollectionChanged(NotifyCollectionChangedAction.Remove, items, index); + } + + /// + /// Raises the PropertyChanged event. + /// + /// The name of the property that changed. + protected void OnPropertyChanged(string propertyName) + { + var evt = PropertyChanged; + evt?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } - private void OnCountPropertyChanged() => OnPropertyChanged("Count"); + private void OnCountPropertyChanged() => OnPropertyChanged("Count"); - // This must agree with Binding.IndexerName. It is declared separately - // here so as to avoid a dependency on PresentationFramework.dll. - private void OnIndexerPropertyChanged() => OnPropertyChanged("Item[]"); + // This must agree with Binding.IndexerName. It is declared separately + // here so as to avoid a dependency on PresentationFramework.dll. + private void OnIndexerPropertyChanged() => OnPropertyChanged("Item[]"); - private void OnCollectionChanged(NotifyCollectionChangedAction action, IList items, int index) => OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, (IList)items, index)); - private void OnCollectionChanged(NotifyCollectionChangedAction action, object item, int index) => OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, item, index)); - private void OnCollectionChanged(NotifyCollectionChangedAction action, object item, int index, int oldIndex) => OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, item, index, oldIndex)); - private void OnCollectionChanged(NotifyCollectionChangedAction action, object oldItem, object newItem, int index) => OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, newItem, oldItem, index)); + private void OnCollectionChanged(NotifyCollectionChangedAction action, IList items, int index) => OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, (IList)items, index)); + private void OnCollectionChanged(NotifyCollectionChangedAction action, object item, int index) => OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, item, index)); + private void OnCollectionChanged(NotifyCollectionChangedAction action, object item, int index, int oldIndex) => OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, item, index, oldIndex)); + private void OnCollectionChanged(NotifyCollectionChangedAction action, object oldItem, object newItem, int index) => OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, newItem, oldItem, index)); - /// - /// Raises the CollectionChanged event. - /// - /// Details about the change. - private void OnCollectionChanged(NotifyCollectionChangedEventArgs args) - { - if (args.OldItems?.Count > 0) - _removedRange?.Invoke(args.OldItems.Cast()); + /// + /// Raises the CollectionChanged event. + /// + /// Details about the change. + private void OnCollectionChanged(NotifyCollectionChangedEventArgs args) + { + if (args.OldItems?.Count > 0) + _removedRange?.Invoke(args.OldItems.Cast()); - if (args.NewItems?.Count > 0) - _addedRange?.Invoke(args.NewItems.Cast()); + if (args.NewItems?.Count > 0) + _addedRange?.Invoke(args.NewItems.Cast()); - var evt = CollectionChanged; - if (evt == null) - return; + var evt = CollectionChanged; + if (evt == null) + return; - ProcessCollectionChanged(evt.GetInvocationList().Cast(), args); - } + ProcessCollectionChanged(evt.GetInvocationList().Cast(), args); + } - /// - /// Processes the NotifyCollectionChangedEventHandler subscribers. - /// - /// The collection of subscribers. - /// Details about the change. - protected virtual void ProcessCollectionChanged(IEnumerable subscribers, NotifyCollectionChangedEventArgs args) - { - foreach (var subscriber in subscribers) - subscriber(this, args); - } + /// + /// Processes the NotifyCollectionChangedEventHandler subscribers. + /// + /// The collection of subscribers. + /// Details about the change. + protected virtual void ProcessCollectionChanged(IEnumerable subscribers, NotifyCollectionChangedEventArgs args) + { + foreach (var subscriber in subscribers) + subscriber(this, args); + } - /// - /// Raises the CollectionChanged event with a reset action. - /// - private void OnCollectionReset() - { - var evt = CollectionChanged; - evt?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - } + /// + /// Raises the CollectionChanged event with a reset action. + /// + private void OnCollectionReset() + { + var evt = CollectionChanged; + evt?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } } diff --git a/ComponentModel/ObservableDictionary.cs b/ComponentModel/ObservableDictionary.cs index 50842d65..014c847e 100644 --- a/ComponentModel/ObservableDictionary.cs +++ b/ComponentModel/ObservableDictionary.cs @@ -1,939 +1,938 @@ -namespace Ecng.ComponentModel +namespace Ecng.ComponentModel; + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Runtime.InteropServices; +using System.Runtime.Serialization; + +// http://drwpf.com/blog/2007/09/16/can-i-bind-my-itemscontrol-to-a-dictionary/ + +/// +/// Represents an observable dictionary that raises notifications when items are added, removed, or refreshed. +/// +/// The type of keys in the dictionary. +/// The type of values in the dictionary. +[Serializable] +public class ObservableDictionary : + IDictionary, + IDictionary, + ISerializable, + IDeserializationCallback, + INotifyCollectionChanged, + INotifyPropertyChanged { - using System; - using System.Collections; - using System.Collections.Generic; - using System.Collections.ObjectModel; - using System.Collections.Specialized; - using System.ComponentModel; - using System.Runtime.InteropServices; - using System.Runtime.Serialization; - - // http://drwpf.com/blog/2007/09/16/can-i-bind-my-itemscontrol-to-a-dictionary/ - - /// - /// Represents an observable dictionary that raises notifications when items are added, removed, or refreshed. - /// - /// The type of keys in the dictionary. - /// The type of values in the dictionary. - [Serializable] - public class ObservableDictionary : - IDictionary, - IDictionary, - ISerializable, - IDeserializationCallback, - INotifyCollectionChanged, - INotifyPropertyChanged - { - #region constructors + #region constructors - #region public + #region public - /// - /// Initializes a new instance of the ObservableDictionary class. - /// - public ObservableDictionary() - { - _keyedEntryCollection = new KeyedDictionaryEntryCollection(); - } + /// + /// Initializes a new instance of the ObservableDictionary class. + /// + public ObservableDictionary() + { + _keyedEntryCollection = new KeyedDictionaryEntryCollection(); + } - /// - /// Initializes a new instance of the ObservableDictionary class with the specified dictionary. - /// - /// The dictionary whose elements are copied to the new ObservableDictionary. - public ObservableDictionary(IDictionary dictionary) - { - _keyedEntryCollection = new KeyedDictionaryEntryCollection(); + /// + /// Initializes a new instance of the ObservableDictionary class with the specified dictionary. + /// + /// The dictionary whose elements are copied to the new ObservableDictionary. + public ObservableDictionary(IDictionary dictionary) + { + _keyedEntryCollection = new KeyedDictionaryEntryCollection(); - foreach (var entry in dictionary) - DoAddEntry(entry.Key, entry.Value); - } + foreach (var entry in dictionary) + DoAddEntry(entry.Key, entry.Value); + } - /// - /// Initializes a new instance of the ObservableDictionary class that uses the specified key comparer. - /// - /// The comparer to use when comparing keys. - public ObservableDictionary(IEqualityComparer comparer) - { - _keyedEntryCollection = new KeyedDictionaryEntryCollection(comparer); - } + /// + /// Initializes a new instance of the ObservableDictionary class that uses the specified key comparer. + /// + /// The comparer to use when comparing keys. + public ObservableDictionary(IEqualityComparer comparer) + { + _keyedEntryCollection = new KeyedDictionaryEntryCollection(comparer); + } - /// - /// Initializes a new instance of the ObservableDictionary class with the specified dictionary and key comparer. - /// - /// The dictionary whose elements are copied to the new ObservableDictionary. - /// The comparer to use when comparing keys. - public ObservableDictionary(IDictionary dictionary, IEqualityComparer comparer) - { - _keyedEntryCollection = new KeyedDictionaryEntryCollection(comparer); + /// + /// Initializes a new instance of the ObservableDictionary class with the specified dictionary and key comparer. + /// + /// The dictionary whose elements are copied to the new ObservableDictionary. + /// The comparer to use when comparing keys. + public ObservableDictionary(IDictionary dictionary, IEqualityComparer comparer) + { + _keyedEntryCollection = new KeyedDictionaryEntryCollection(comparer); - foreach (var entry in dictionary) - DoAddEntry(entry.Key, entry.Value); - } + foreach (var entry in dictionary) + DoAddEntry(entry.Key, entry.Value); + } - #endregion public + #endregion public - #region protected + #region protected - /// - /// Initializes a new instance of the ObservableDictionary class from serialization data. - /// - /// The SerializationInfo to populate with data. - /// The destination for this serialization. - protected ObservableDictionary(SerializationInfo info, StreamingContext context) - { - _siInfo = info; - } + /// + /// Initializes a new instance of the ObservableDictionary class from serialization data. + /// + /// The SerializationInfo to populate with data. + /// The destination for this serialization. + protected ObservableDictionary(SerializationInfo info, StreamingContext context) + { + _siInfo = info; + } - #endregion protected + #endregion protected - #endregion constructors + #endregion constructors - #region properties + #region properties - #region public + #region public - /// - /// Gets the comparer used to compare keys in the dictionary. - /// - public IEqualityComparer Comparer => _keyedEntryCollection.Comparer; + /// + /// Gets the comparer used to compare keys in the dictionary. + /// + public IEqualityComparer Comparer => _keyedEntryCollection.Comparer; - /// - /// Gets the number of key-value pairs contained in the dictionary. - /// - public int Count => _keyedEntryCollection.Count; + /// + /// Gets the number of key-value pairs contained in the dictionary. + /// + public int Count => _keyedEntryCollection.Count; - /// - /// Gets the collection of keys in the dictionary. - /// - public Dictionary.KeyCollection Keys => TrueDictionary.Keys; + /// + /// Gets the collection of keys in the dictionary. + /// + public Dictionary.KeyCollection Keys => TrueDictionary.Keys; - /// - /// Gets or sets the value associated with the specified key. - /// - /// The key of the value to get or set. - public TValue this[TKey key] - { - get => (TValue)_keyedEntryCollection[key].Value; - set => DoSetEntry(key, value); - } + /// + /// Gets or sets the value associated with the specified key. + /// + /// The key of the value to get or set. + public TValue this[TKey key] + { + get => (TValue)_keyedEntryCollection[key].Value; + set => DoSetEntry(key, value); + } - /// - /// Gets the collection of values in the dictionary. - /// - public Dictionary.ValueCollection Values => TrueDictionary.Values; + /// + /// Gets the collection of values in the dictionary. + /// + public Dictionary.ValueCollection Values => TrueDictionary.Values; - #endregion public + #endregion public - #region private + #region private - private Dictionary TrueDictionary + private Dictionary TrueDictionary + { + get { - get + if (_dictionaryCacheVersion != _version) { - if (_dictionaryCacheVersion != _version) - { - _dictionaryCache.Clear(); - foreach (DictionaryEntry entry in _keyedEntryCollection) - _dictionaryCache.Add((TKey)entry.Key, (TValue)entry.Value); - _dictionaryCacheVersion = _version; - } - return _dictionaryCache; + _dictionaryCache.Clear(); + foreach (DictionaryEntry entry in _keyedEntryCollection) + _dictionaryCache.Add((TKey)entry.Key, (TValue)entry.Value); + _dictionaryCacheVersion = _version; } + return _dictionaryCache; } + } - #endregion private + #endregion private - #endregion properties + #endregion properties - #region methods + #region methods - #region public + #region public - /// - /// Adds the specified key and value to the dictionary. - /// - /// The key of the element to add. - /// The value of the element to add. - public void Add(TKey key, TValue value) - { - DoAddEntry(key, value); - } + /// + /// Adds the specified key and value to the dictionary. + /// + /// The key of the element to add. + /// The value of the element to add. + public void Add(TKey key, TValue value) + { + DoAddEntry(key, value); + } - /// - /// Removes all keys and values from the dictionary. - /// - public void Clear() - { - DoClearEntries(); - } + /// + /// Removes all keys and values from the dictionary. + /// + public void Clear() + { + DoClearEntries(); + } - /// - /// Determines whether the dictionary contains the specified key. - /// - /// The key to locate in the dictionary. - public bool ContainsKey(TKey key) - { - return _keyedEntryCollection.Contains(key); - } + /// + /// Determines whether the dictionary contains the specified key. + /// + /// The key to locate in the dictionary. + public bool ContainsKey(TKey key) + { + return _keyedEntryCollection.Contains(key); + } - /// - /// Determines whether the dictionary contains a specific value. - /// - /// The value to locate in the dictionary. - public bool ContainsValue(TValue value) - { - return TrueDictionary.ContainsValue(value); - } + /// + /// Determines whether the dictionary contains a specific value. + /// + /// The value to locate in the dictionary. + public bool ContainsValue(TValue value) + { + return TrueDictionary.ContainsValue(value); + } - /// - /// Returns an enumerator that iterates through the dictionary. - /// - public IEnumerator GetEnumerator() - { - return new Enumerator(this, false); - } + /// + /// Returns an enumerator that iterates through the dictionary. + /// + public IEnumerator GetEnumerator() + { + return new Enumerator(this, false); + } - /// - /// Removes the value with the specified key from the dictionary. - /// - /// The key of the element to remove. - public bool Remove(TKey key) - { - return DoRemoveEntry(key); - } + /// + /// Removes the value with the specified key from the dictionary. + /// + /// The key of the element to remove. + public bool Remove(TKey key) + { + return DoRemoveEntry(key); + } - /// - /// Gets the value associated with the specified key. - /// - /// The key of the element to locate. - /// When this method returns, the value associated with the specified key, if found; otherwise, the default value for the type. - public bool TryGetValue(TKey key, out TValue value) - { - var result = _keyedEntryCollection.Contains(key); - value = result ? (TValue)_keyedEntryCollection[key].Value : default; - return result; - } + /// + /// Gets the value associated with the specified key. + /// + /// The key of the element to locate. + /// When this method returns, the value associated with the specified key, if found; otherwise, the default value for the type. + public bool TryGetValue(TKey key, out TValue value) + { + var result = _keyedEntryCollection.Contains(key); + value = result ? (TValue)_keyedEntryCollection[key].Value : default; + return result; + } - #endregion public + #endregion public - #region protected + #region protected - /// - /// Called before adding an entry to the dictionary. - /// - /// The key for the entry to add. - /// The value for the entry to add. - /// True if the entry is added; otherwise, false. - protected virtual bool AddEntry(TKey key, TValue value) - { - _keyedEntryCollection.Add(new DictionaryEntry(key, value)); - return true; - } + /// + /// Called before adding an entry to the dictionary. + /// + /// The key for the entry to add. + /// The value for the entry to add. + /// True if the entry is added; otherwise, false. + protected virtual bool AddEntry(TKey key, TValue value) + { + _keyedEntryCollection.Add(new DictionaryEntry(key, value)); + return true; + } - /// - /// Called to clear all entries from the dictionary. - /// - /// True if there were entries to clear; otherwise, false. - protected virtual bool ClearEntries() + /// + /// Called to clear all entries from the dictionary. + /// + /// True if there were entries to clear; otherwise, false. + protected virtual bool ClearEntries() + { + bool result = (Count > 0); + if (result) { - bool result = (Count > 0); - if (result) - { - _keyedEntryCollection.Clear(); - } - return result; + _keyedEntryCollection.Clear(); } + return result; + } - /// - /// Retrieves the index and entry associated with the specified key. - /// - /// The key to locate in the dictionary. - /// When this method returns, contains the DictionaryEntry for the specified key, if found. - /// The index of the entry if found; otherwise, -1. - protected int GetIndexAndEntryForKey(TKey key, out DictionaryEntry entry) + /// + /// Retrieves the index and entry associated with the specified key. + /// + /// The key to locate in the dictionary. + /// When this method returns, contains the DictionaryEntry for the specified key, if found. + /// The index of the entry if found; otherwise, -1. + protected int GetIndexAndEntryForKey(TKey key, out DictionaryEntry entry) + { + entry = new DictionaryEntry(); + int index = -1; + if (_keyedEntryCollection.Contains(key)) { - entry = new DictionaryEntry(); - int index = -1; - if (_keyedEntryCollection.Contains(key)) - { - entry = _keyedEntryCollection[key]; - index = _keyedEntryCollection.IndexOf(entry); - } - return index; + entry = _keyedEntryCollection[key]; + index = _keyedEntryCollection.IndexOf(entry); } + return index; + } - /// - /// Raises the CollectionChanged event with the provided arguments. - /// - /// Details of the change. - protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args) - { - _collectionChanged?.Invoke(this, args); - } + /// + /// Raises the CollectionChanged event with the provided arguments. + /// + /// Details of the change. + protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args) + { + _collectionChanged?.Invoke(this, args); + } - /// - /// Raises the PropertyChanged event for the specified property name. - /// - /// The name of the property that changed. - protected virtual void OnPropertyChanged(string name) - { - _propertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); - } + /// + /// Raises the PropertyChanged event for the specified property name. + /// + /// The name of the property that changed. + protected virtual void OnPropertyChanged(string name) + { + _propertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); + } - /// - /// Called to remove an entry with the specified key from the dictionary. - /// - /// The key of the entry to remove. - /// True if the entry was removed; otherwise, false. - protected virtual bool RemoveEntry(TKey key) - { - return _keyedEntryCollection.Remove(key); - } + /// + /// Called to remove an entry with the specified key from the dictionary. + /// + /// The key of the entry to remove. + /// True if the entry was removed; otherwise, false. + protected virtual bool RemoveEntry(TKey key) + { + return _keyedEntryCollection.Remove(key); + } - /// - /// Called to set the value for an entry with the specified key in the dictionary. - /// - /// The key of the entry to update. - /// The new value to set. - /// True if the value was changed; otherwise, false. - protected virtual bool SetEntry(TKey key, TValue value) - { - var keyExists = _keyedEntryCollection.Contains(key); + /// + /// Called to set the value for an entry with the specified key in the dictionary. + /// + /// The key of the entry to update. + /// The new value to set. + /// True if the value was changed; otherwise, false. + protected virtual bool SetEntry(TKey key, TValue value) + { + var keyExists = _keyedEntryCollection.Contains(key); - // if identical key/value pair already exists, nothing to do - if (keyExists && value.Equals((TValue)_keyedEntryCollection[key].Value)) - return false; + // if identical key/value pair already exists, nothing to do + if (keyExists && value.Equals((TValue)_keyedEntryCollection[key].Value)) + return false; - // otherwise, remove the existing entry - if (keyExists) - _keyedEntryCollection.Remove(key); + // otherwise, remove the existing entry + if (keyExists) + _keyedEntryCollection.Remove(key); - // add the new entry - _keyedEntryCollection.Add(new DictionaryEntry(key, value)); + // add the new entry + _keyedEntryCollection.Add(new DictionaryEntry(key, value)); - return true; - } + return true; + } - #endregion protected + #endregion protected - #region private + #region private - private void DoAddEntry(TKey key, TValue value) + private void DoAddEntry(TKey key, TValue value) + { + if (AddEntry(key, value)) { - if (AddEntry(key, value)) - { - _version++; + _version++; - var index = GetIndexAndEntryForKey(key, out var entry); - FireEntryAddedNotifications(entry, index); - } + var index = GetIndexAndEntryForKey(key, out var entry); + FireEntryAddedNotifications(entry, index); } + } - private void DoClearEntries() + private void DoClearEntries() + { + if (ClearEntries()) { - if (ClearEntries()) - { - _version++; - FireResetNotifications(); - } + _version++; + FireResetNotifications(); } + } - private bool DoRemoveEntry(TKey key) + private bool DoRemoveEntry(TKey key) + { + var index = GetIndexAndEntryForKey(key, out var entry); + + var result = RemoveEntry(key); + if (result) { - var index = GetIndexAndEntryForKey(key, out var entry); + _version++; + if (index > -1) + FireEntryRemovedNotifications(entry, index); + } - var result = RemoveEntry(key); - if (result) - { - _version++; - if (index > -1) - FireEntryRemovedNotifications(entry, index); - } + return result; + } - return result; - } + private void DoSetEntry(TKey key, TValue value) + { + var index = GetIndexAndEntryForKey(key, out var entry); - private void DoSetEntry(TKey key, TValue value) + if (SetEntry(key, value)) { - var index = GetIndexAndEntryForKey(key, out var entry); + _version++; - if (SetEntry(key, value)) + // if prior entry existed for this key, fire the removed notifications + if (index > -1) { - _version++; - - // if prior entry existed for this key, fire the removed notifications - if (index > -1) - { - FireEntryRemovedNotifications(entry, index); + FireEntryRemovedNotifications(entry, index); - // force the property change notifications to fire for the modified entry - _countCache--; - } - - // then fire the added notifications - index = GetIndexAndEntryForKey(key, out entry); - FireEntryAddedNotifications(entry, index); + // force the property change notifications to fire for the modified entry + _countCache--; } + + // then fire the added notifications + index = GetIndexAndEntryForKey(key, out entry); + FireEntryAddedNotifications(entry, index); } + } - private void FireEntryAddedNotifications(DictionaryEntry entry, int index) - { - // fire the relevant PropertyChanged notifications - FirePropertyChangedNotifications(); + private void FireEntryAddedNotifications(DictionaryEntry entry, int index) + { + // fire the relevant PropertyChanged notifications + FirePropertyChangedNotifications(); - // fire CollectionChanged notification - OnCollectionChanged(index > -1 - ? new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new KeyValuePair((TKey)entry.Key, (TValue)entry.Value), index) - : new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - } + // fire CollectionChanged notification + OnCollectionChanged(index > -1 + ? new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new KeyValuePair((TKey)entry.Key, (TValue)entry.Value), index) + : new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } - private void FireEntryRemovedNotifications(DictionaryEntry entry, int index) - { - // fire the relevant PropertyChanged notifications - FirePropertyChangedNotifications(); + private void FireEntryRemovedNotifications(DictionaryEntry entry, int index) + { + // fire the relevant PropertyChanged notifications + FirePropertyChangedNotifications(); - // fire CollectionChanged notification - OnCollectionChanged(index > -1 - ? new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new KeyValuePair((TKey)entry.Key, (TValue)entry.Value), index) - : new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - } + // fire CollectionChanged notification + OnCollectionChanged(index > -1 + ? new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new KeyValuePair((TKey)entry.Key, (TValue)entry.Value), index) + : new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } - private void FirePropertyChangedNotifications() + private void FirePropertyChangedNotifications() + { + if (Count != _countCache) { - if (Count != _countCache) - { - _countCache = Count; - OnPropertyChanged("Count"); - OnPropertyChanged("Item[]"); - OnPropertyChanged("Keys"); - OnPropertyChanged("Values"); - } + _countCache = Count; + OnPropertyChanged("Count"); + OnPropertyChanged("Item[]"); + OnPropertyChanged("Keys"); + OnPropertyChanged("Values"); } + } - private void FireResetNotifications() - { - // fire the relevant PropertyChanged notifications - FirePropertyChangedNotifications(); + private void FireResetNotifications() + { + // fire the relevant PropertyChanged notifications + FirePropertyChangedNotifications(); - // fire CollectionChanged notification - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - } + // fire CollectionChanged notification + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } - #endregion private + #endregion private - #endregion methods + #endregion methods - #region interfaces + #region interfaces - #region IDictionary + #region IDictionary - /// - /// Adds an element with the provided key and value to the dictionary. - /// - void IDictionary.Add(TKey key, TValue value) - { - DoAddEntry(key, value); - } + /// + /// Adds an element with the provided key and value to the dictionary. + /// + void IDictionary.Add(TKey key, TValue value) + { + DoAddEntry(key, value); + } - /// - /// Removes the element with the specified key from the dictionary. - /// - bool IDictionary.Remove(TKey key) - { - return DoRemoveEntry(key); - } + /// + /// Removes the element with the specified key from the dictionary. + /// + bool IDictionary.Remove(TKey key) + { + return DoRemoveEntry(key); + } + + /// + /// Determines whether the dictionary contains the specified key. + /// + bool IDictionary.ContainsKey(TKey key) + { + return _keyedEntryCollection.Contains(key); + } + + /// + /// Gets the value associated with the specified key. + /// + bool IDictionary.TryGetValue(TKey key, out TValue value) + { + return TryGetValue(key, out value); + } + + /// + /// Gets a collection containing the keys in the dictionary. + /// + ICollection IDictionary.Keys => Keys; + + /// + /// Gets a collection containing the values in the dictionary. + /// + ICollection IDictionary.Values => Values; + + /// + /// Gets or sets the element with the specified key. + /// + TValue IDictionary.this[TKey key] + { + get => (TValue)_keyedEntryCollection[key].Value; + set => DoSetEntry(key, value); + } + + #endregion IDictionary + + #region IDictionary + + /// + /// Adds an element with the provided key and value to the dictionary. + /// + void IDictionary.Add(object key, object value) + { + DoAddEntry((TKey)key, (TValue)value); + } + + /// + /// Removes all elements from the dictionary. + /// + void IDictionary.Clear() + { + DoClearEntries(); + } + + /// + /// Determines whether the dictionary contains an element with the specified key. + /// + bool IDictionary.Contains(object key) + { + return _keyedEntryCollection.Contains((TKey)key); + } + + /// + /// Returns an IDictionaryEnumerator for the dictionary. + /// + IDictionaryEnumerator IDictionary.GetEnumerator() + { + return new Enumerator(this, true); + } + + /// + /// Gets a value indicating whether the dictionary has a fixed size. + /// + bool IDictionary.IsFixedSize => false; + + /// + /// Gets a value indicating whether the dictionary is read-only. + /// + bool IDictionary.IsReadOnly => false; + + /// + /// Gets or sets the element with the specified key. + /// + object IDictionary.this[object key] + { + get => _keyedEntryCollection[(TKey)key].Value; + set => DoSetEntry((TKey)key, (TValue)value); + } - /// - /// Determines whether the dictionary contains the specified key. - /// - bool IDictionary.ContainsKey(TKey key) - { - return _keyedEntryCollection.Contains(key); - } + /// + /// Gets a collection containing the keys in the dictionary. + /// + ICollection IDictionary.Keys => Keys; - /// - /// Gets the value associated with the specified key. - /// - bool IDictionary.TryGetValue(TKey key, out TValue value) - { - return TryGetValue(key, out value); - } + /// + /// Removes the element with the specified key from the dictionary. + /// + void IDictionary.Remove(object key) + { + DoRemoveEntry((TKey)key); + } - /// - /// Gets a collection containing the keys in the dictionary. - /// - ICollection IDictionary.Keys => Keys; + /// + /// Gets a collection containing the values in the dictionary. + /// + ICollection IDictionary.Values => Values; - /// - /// Gets a collection containing the values in the dictionary. - /// - ICollection IDictionary.Values => Values; + #endregion IDictionary - /// - /// Gets or sets the element with the specified key. - /// - TValue IDictionary.this[TKey key] - { - get => (TValue)_keyedEntryCollection[key].Value; - set => DoSetEntry(key, value); - } + #region ICollection> - #endregion IDictionary + /// + /// Adds the specified key-value pair to the dictionary. + /// + void ICollection>.Add(KeyValuePair kvp) + { + DoAddEntry(kvp.Key, kvp.Value); + } - #region IDictionary + /// + /// Removes all key-value pairs from the dictionary. + /// + void ICollection>.Clear() + { + DoClearEntries(); + } - /// - /// Adds an element with the provided key and value to the dictionary. - /// - void IDictionary.Add(object key, object value) - { - DoAddEntry((TKey)key, (TValue)value); - } + /// + /// Determines whether the dictionary contains the specified key-value pair. + /// + bool ICollection>.Contains(KeyValuePair kvp) + { + return _keyedEntryCollection.Contains(kvp.Key); + } - /// - /// Removes all elements from the dictionary. - /// - void IDictionary.Clear() + /// + /// Copies the key-value pairs of the dictionary to the specified array starting at the given index. + /// + void ICollection>.CopyTo(KeyValuePair[] array, int index) + { + if (array == null) { - DoClearEntries(); + throw new ArgumentNullException(nameof(index), "CopyTo() failed: array parameter was null"); } - - /// - /// Determines whether the dictionary contains an element with the specified key. - /// - bool IDictionary.Contains(object key) + if ((index < 0) || (index > array.Length)) { - return _keyedEntryCollection.Contains((TKey)key); + throw new ArgumentOutOfRangeException(nameof(index), "CopyTo() failed: index parameter was outside the bounds of the supplied array"); } - - /// - /// Returns an IDictionaryEnumerator for the dictionary. - /// - IDictionaryEnumerator IDictionary.GetEnumerator() + if ((array.Length - index) < _keyedEntryCollection.Count) { - return new Enumerator(this, true); + throw new ArgumentException("CopyTo() failed: supplied array was too small", nameof(index)); } - /// - /// Gets a value indicating whether the dictionary has a fixed size. - /// - bool IDictionary.IsFixedSize => false; + foreach (DictionaryEntry entry in _keyedEntryCollection) + array[index++] = new KeyValuePair((TKey)entry.Key, (TValue)entry.Value); + } - /// - /// Gets a value indicating whether the dictionary is read-only. - /// - bool IDictionary.IsReadOnly => false; + /// + /// Gets the number of key-value pairs contained in the dictionary. + /// + int ICollection>.Count => _keyedEntryCollection.Count; - /// - /// Gets or sets the element with the specified key. - /// - object IDictionary.this[object key] - { - get => _keyedEntryCollection[(TKey)key].Value; - set => DoSetEntry((TKey)key, (TValue)value); - } + /// + /// Gets a value indicating whether the dictionary is read-only. + /// + bool ICollection>.IsReadOnly => false; - /// - /// Gets a collection containing the keys in the dictionary. - /// - ICollection IDictionary.Keys => Keys; + /// + /// Removes the specified key-value pair from the dictionary. + /// + bool ICollection>.Remove(KeyValuePair kvp) + { + return DoRemoveEntry(kvp.Key); + } - /// - /// Removes the element with the specified key from the dictionary. - /// - void IDictionary.Remove(object key) - { - DoRemoveEntry((TKey)key); - } + #endregion ICollection> - /// - /// Gets a collection containing the values in the dictionary. - /// - ICollection IDictionary.Values => Values; + #region ICollection - #endregion IDictionary + /// + /// Copies the elements of the dictionary to an Array, starting at a particular Array index. + /// + void ICollection.CopyTo(Array array, int index) + { + ((ICollection)_keyedEntryCollection).CopyTo(array, index); + } - #region ICollection> + /// + /// Gets the number of key-value pairs in the dictionary. + /// + int ICollection.Count => _keyedEntryCollection.Count; - /// - /// Adds the specified key-value pair to the dictionary. - /// - void ICollection>.Add(KeyValuePair kvp) - { - DoAddEntry(kvp.Key, kvp.Value); - } + /// + /// Gets a value indicating whether access to the dictionary is synchronized (thread safe). + /// + bool ICollection.IsSynchronized => ((ICollection)_keyedEntryCollection).IsSynchronized; - /// - /// Removes all key-value pairs from the dictionary. - /// - void ICollection>.Clear() - { - DoClearEntries(); - } + /// + /// Gets an object that can be used to synchronize access to the dictionary. + /// + object ICollection.SyncRoot => ((ICollection)_keyedEntryCollection).SyncRoot; - /// - /// Determines whether the dictionary contains the specified key-value pair. - /// - bool ICollection>.Contains(KeyValuePair kvp) - { - return _keyedEntryCollection.Contains(kvp.Key); - } + #endregion ICollection - /// - /// Copies the key-value pairs of the dictionary to the specified array starting at the given index. - /// - void ICollection>.CopyTo(KeyValuePair[] array, int index) - { - if (array == null) - { - throw new ArgumentNullException(nameof(index), "CopyTo() failed: array parameter was null"); - } - if ((index < 0) || (index > array.Length)) - { - throw new ArgumentOutOfRangeException(nameof(index), "CopyTo() failed: index parameter was outside the bounds of the supplied array"); - } - if ((array.Length - index) < _keyedEntryCollection.Count) - { - throw new ArgumentException("CopyTo() failed: supplied array was too small", nameof(index)); - } + #region IEnumerable> - foreach (DictionaryEntry entry in _keyedEntryCollection) - array[index++] = new KeyValuePair((TKey)entry.Key, (TValue)entry.Value); - } + /// + /// Returns an enumerator that iterates through the dictionary. + /// + IEnumerator> IEnumerable>.GetEnumerator() + { + return new Enumerator(this, false); + } - /// - /// Gets the number of key-value pairs contained in the dictionary. - /// - int ICollection>.Count => _keyedEntryCollection.Count; + #endregion IEnumerable> - /// - /// Gets a value indicating whether the dictionary is read-only. - /// - bool ICollection>.IsReadOnly => false; + #region IEnumerable - /// - /// Removes the specified key-value pair from the dictionary. - /// - bool ICollection>.Remove(KeyValuePair kvp) - { - return DoRemoveEntry(kvp.Key); - } + /// + /// Returns an enumerator that iterates through the dictionary. + /// + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } - #endregion ICollection> + #endregion IEnumerable - #region ICollection + #region ISerializable - /// - /// Copies the elements of the dictionary to an Array, starting at a particular Array index. - /// - void ICollection.CopyTo(Array array, int index) + /// + /// Populates a SerializationInfo with the data needed to serialize the dictionary. + /// + public virtual void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info == null) { - ((ICollection)_keyedEntryCollection).CopyTo(array, index); + throw new ArgumentNullException(nameof(info)); } - /// - /// Gets the number of key-value pairs in the dictionary. - /// - int ICollection.Count => _keyedEntryCollection.Count; - - /// - /// Gets a value indicating whether access to the dictionary is synchronized (thread safe). - /// - bool ICollection.IsSynchronized => ((ICollection)_keyedEntryCollection).IsSynchronized; - - /// - /// Gets an object that can be used to synchronize access to the dictionary. - /// - object ICollection.SyncRoot => ((ICollection)_keyedEntryCollection).SyncRoot; + var entries = new Collection(); + foreach (var entry in _keyedEntryCollection) + entries.Add(entry); + info.AddValue("entries", entries); + } - #endregion ICollection + #endregion ISerializable - #region IEnumerable> + #region IDeserializationCallback - /// - /// Returns an enumerator that iterates through the dictionary. - /// - IEnumerator> IEnumerable>.GetEnumerator() + /// + /// Runs when the entire object graph has been deserialized. + /// + public virtual void OnDeserialization(object sender) + { + if (_siInfo != null) { - return new Enumerator(this, false); + var entries = (Collection) + _siInfo.GetValue("entries", typeof(Collection)); + foreach (var entry in entries) + AddEntry((TKey)entry.Key, (TValue)entry.Value); } + } - #endregion IEnumerable> - - #region IEnumerable - - /// - /// Returns an enumerator that iterates through the dictionary. - /// - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + #endregion IDeserializationCallback - #endregion IEnumerable + #region INotifyCollectionChanged - #region ISerializable + /// + /// Occurs when the collection changes. + /// + event NotifyCollectionChangedEventHandler INotifyCollectionChanged.CollectionChanged + { + add => _collectionChanged += value; + remove => _collectionChanged -= value; + } - /// - /// Populates a SerializationInfo with the data needed to serialize the dictionary. - /// - public virtual void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - { - throw new ArgumentNullException(nameof(info)); - } + private NotifyCollectionChangedEventHandler _collectionChanged; - var entries = new Collection(); - foreach (var entry in _keyedEntryCollection) - entries.Add(entry); - info.AddValue("entries", entries); - } + #endregion INotifyCollectionChanged - #endregion ISerializable + #region INotifyPropertyChanged - #region IDeserializationCallback + /// + /// Occurs when a property value changes. + /// + event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged + { + add => _propertyChanged += value; + remove => _propertyChanged -= value; + } - /// - /// Runs when the entire object graph has been deserialized. - /// - public virtual void OnDeserialization(object sender) - { - if (_siInfo != null) - { - var entries = (Collection) - _siInfo.GetValue("entries", typeof(Collection)); - foreach (var entry in entries) - AddEntry((TKey)entry.Key, (TValue)entry.Value); - } - } + private PropertyChangedEventHandler _propertyChanged; - #endregion IDeserializationCallback + #endregion INotifyPropertyChanged - #region INotifyCollectionChanged + #endregion interfaces - /// - /// Occurs when the collection changes. - /// - event NotifyCollectionChangedEventHandler INotifyCollectionChanged.CollectionChanged - { - add => _collectionChanged += value; - remove => _collectionChanged -= value; - } + #region protected classes - private NotifyCollectionChangedEventHandler _collectionChanged; + #region KeyedDictionaryEntryCollection - #endregion INotifyCollectionChanged + /// + /// Represents a keyed collection of DictionaryEntry items using TKey as the key. + /// + protected class KeyedDictionaryEntryCollection : KeyedCollection + { + #region constructors - #region INotifyPropertyChanged + #region public /// - /// Occurs when a property value changes. + /// Initializes a new instance of the KeyedDictionaryEntryCollection class. /// - event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged - { - add => _propertyChanged += value; - remove => _propertyChanged -= value; - } + public KeyedDictionaryEntryCollection() { } - private PropertyChangedEventHandler _propertyChanged; + /// + /// Initializes a new instance of the KeyedDictionaryEntryCollection class with the specified comparer. + /// + /// The comparer to use for key comparisons. + public KeyedDictionaryEntryCollection(IEqualityComparer comparer) : base(comparer) { } - #endregion INotifyPropertyChanged + #endregion public - #endregion interfaces + #endregion constructors - #region protected classes + #region methods - #region KeyedDictionaryEntryCollection + #region protected /// - /// Represents a keyed collection of DictionaryEntry items using TKey as the key. + /// Extracts the key from the DictionaryEntry. /// - protected class KeyedDictionaryEntryCollection : KeyedCollection + /// The DictionaryEntry for which to get the key. + /// The key associated with the entry. + protected override TKey GetKeyForItem(DictionaryEntry entry) { - #region constructors + return (TKey)entry.Key; + } - #region public + #endregion protected - /// - /// Initializes a new instance of the KeyedDictionaryEntryCollection class. - /// - public KeyedDictionaryEntryCollection() { } + #endregion methods + } - /// - /// Initializes a new instance of the KeyedDictionaryEntryCollection class with the specified comparer. - /// - /// The comparer to use for key comparisons. - public KeyedDictionaryEntryCollection(IEqualityComparer comparer) : base(comparer) { } + #endregion KeyedDictionaryEntryCollection - #endregion public + #endregion protected classes - #endregion constructors + #region public structures - #region methods + #region Enumerator - #region protected + [Serializable, StructLayout(LayoutKind.Sequential)] + private struct Enumerator(ObservableDictionary dictionary, bool isDictionaryEntryEnumerator) : IEnumerator>, IDictionaryEnumerator + { + #region properties - /// - /// Extracts the key from the DictionaryEntry. - /// - /// The DictionaryEntry for which to get the key. - /// The key associated with the entry. - protected override TKey GetKeyForItem(DictionaryEntry entry) + public readonly KeyValuePair Current + { + get { - return (TKey)entry.Key; + ValidateCurrent(); + return _current; } - - #endregion protected - - #endregion methods } - #endregion KeyedDictionaryEntryCollection - - #endregion protected classes - - #region public structures + #endregion properties - #region Enumerator + #region methods - [Serializable, StructLayout(LayoutKind.Sequential)] - private struct Enumerator(ObservableDictionary dictionary, bool isDictionaryEntryEnumerator) : IEnumerator>, IDictionaryEnumerator + public readonly void Dispose() { - #region properties - - public readonly KeyValuePair Current - { - get - { - ValidateCurrent(); - return _current; - } - } - - #endregion properties - - #region methods + GC.SuppressFinalize(this); + } - public readonly void Dispose() + public bool MoveNext() + { + ValidateVersion(); + _index++; + if (_index < dictionary._keyedEntryCollection.Count) { - GC.SuppressFinalize(this); + _current = new KeyValuePair((TKey)dictionary._keyedEntryCollection[_index].Key, (TValue)dictionary._keyedEntryCollection[_index].Value); + return true; } + _index = -2; + _current = new KeyValuePair(); + return false; + } - public bool MoveNext() + private readonly void ValidateCurrent() + { + if (_index == -1) { - ValidateVersion(); - _index++; - if (_index < dictionary._keyedEntryCollection.Count) - { - _current = new KeyValuePair((TKey)dictionary._keyedEntryCollection[_index].Key, (TValue)dictionary._keyedEntryCollection[_index].Value); - return true; - } - _index = -2; - _current = new KeyValuePair(); - return false; + throw new InvalidOperationException("The enumerator has not been started."); } - - private readonly void ValidateCurrent() + else if (_index == -2) { - if (_index == -1) - { - throw new InvalidOperationException("The enumerator has not been started."); - } - else if (_index == -2) - { - throw new InvalidOperationException("The enumerator has reached the end of the collection."); - } + throw new InvalidOperationException("The enumerator has reached the end of the collection."); } + } - private readonly void ValidateVersion() + private readonly void ValidateVersion() + { + if (_version != dictionary._version) { - if (_version != dictionary._version) - { - throw new InvalidOperationException("The enumerator is not valid because the dictionary changed."); - } + throw new InvalidOperationException("The enumerator is not valid because the dictionary changed."); } + } - #endregion methods + #endregion methods - #region IEnumerator implementation + #region IEnumerator implementation - readonly object IEnumerator.Current + readonly object IEnumerator.Current + { + get { - get + ValidateCurrent(); + if (isDictionaryEntryEnumerator) { - ValidateCurrent(); - if (isDictionaryEntryEnumerator) - { - return new DictionaryEntry(_current.Key, _current.Value); - } - return new KeyValuePair(_current.Key, _current.Value); + return new DictionaryEntry(_current.Key, _current.Value); } + return new KeyValuePair(_current.Key, _current.Value); } + } - void IEnumerator.Reset() - { - ValidateVersion(); - _index = -1; - _current = new KeyValuePair(); - } + void IEnumerator.Reset() + { + ValidateVersion(); + _index = -1; + _current = new KeyValuePair(); + } - #endregion IEnumerator implementation + #endregion IEnumerator implementation - #region IDictionaryEnumerator implementation + #region IDictionaryEnumerator implementation - readonly DictionaryEntry IDictionaryEnumerator.Entry + readonly DictionaryEntry IDictionaryEnumerator.Entry + { + get { - get - { - ValidateCurrent(); - return new DictionaryEntry(_current.Key, _current.Value); - } + ValidateCurrent(); + return new DictionaryEntry(_current.Key, _current.Value); } - readonly object IDictionaryEnumerator.Key + } + readonly object IDictionaryEnumerator.Key + { + get { - get - { - ValidateCurrent(); - return _current.Key; - } + ValidateCurrent(); + return _current.Key; } - readonly object IDictionaryEnumerator.Value + } + readonly object IDictionaryEnumerator.Value + { + get { - get - { - ValidateCurrent(); - return _current.Value; - } + ValidateCurrent(); + return _current.Value; } + } - #endregion - #region fields + #endregion + #region fields - private readonly int _version = dictionary._version; - private int _index = -1; - private KeyValuePair _current = new(); + private readonly int _version = dictionary._version; + private int _index = -1; + private KeyValuePair _current = new(); - #endregion fields - } + #endregion fields + } - #endregion Enumerator + #endregion Enumerator - #endregion public structures + #endregion public structures - #region fields + #region fields - private readonly KeyedDictionaryEntryCollection _keyedEntryCollection; + private readonly KeyedDictionaryEntryCollection _keyedEntryCollection; - private int _countCache; - private readonly Dictionary _dictionaryCache = []; - private int _dictionaryCacheVersion; - private int _version; + private int _countCache; + private readonly Dictionary _dictionaryCache = []; + private int _dictionaryCacheVersion; + private int _version; - [NonSerialized] - private readonly SerializationInfo _siInfo; + [NonSerialized] + private readonly SerializationInfo _siInfo; - #endregion fields - } + #endregion fields } \ No newline at end of file diff --git a/ComponentModel/OperatorRegistry.cs b/ComponentModel/OperatorRegistry.cs index c24b8f04..71139944 100644 --- a/ComponentModel/OperatorRegistry.cs +++ b/ComponentModel/OperatorRegistry.cs @@ -1,185 +1,184 @@ -namespace Ecng.ComponentModel -{ - using System; +namespace Ecng.ComponentModel; + +using System; - using Ecng.Collections; - using Ecng.Common; - using Ecng.Localization; +using Ecng.Collections; +using Ecng.Common; +using Ecng.Localization; +/// +/// Provides a registry for operators to perform mathematical and comparison operations. +/// +public static class OperatorRegistry +{ /// - /// Provides a registry for operators to perform mathematical and comparison operations. + /// Represents an operator implementation for types implementing . /// - public static class OperatorRegistry + /// The type that implements . + private sealed class OperableOperator : BaseOperator + where T : IOperable { - /// - /// Represents an operator implementation for types implementing . - /// - /// The type that implements . - private sealed class OperableOperator : BaseOperator - where T : IOperable + /// + public override int Compare(T x, T y) { - /// - public override int Compare(T x, T y) - { - return x.CompareTo(y); - } - - /// - public override T Add(T first, T second) - { - return first.Add(second); - } - - /// - public override T Subtract(T first, T second) - { - return first.Subtract(second); - } - - /// - public override T Multiply(T first, T second) - { - return first.Multiply(second); - } - - /// - public override T Divide(T first, T second) - { - return first.Divide(second); - } + return x.CompareTo(y); } - private static readonly SynchronizedDictionary _operators = []; - - /// - /// Initializes static members of the class. - /// Registers the default set of operators. - /// - static OperatorRegistry() + /// + public override T Add(T first, T second) { - AddOperator(new ByteOperator()); - AddOperator(new ShortOperator()); - AddOperator(new IntOperator()); - AddOperator(new LongOperator()); - AddOperator(new FloatOperator()); - AddOperator(new DoubleOperator()); - AddOperator(new DecimalOperator()); - AddOperator(new TimeSpanOperator()); - AddOperator(new DateTimeOperator()); - AddOperator(new DateTimeOffsetOperator()); - AddOperator(new SByteOperator()); - AddOperator(new UShortOperator()); - AddOperator(new UIntOperator()); - AddOperator(new ULongOperator()); + return first.Add(second); } - /// - /// Adds an operator for the specified type. - /// - /// The type that the operator will handle. - /// The operator to add. - /// Thrown when the is null. - public static void AddOperator(IOperator @operator) + /// + public override T Subtract(T first, T second) { - if (@operator is null) - throw new ArgumentNullException(nameof(@operator)); - - _operators.Add(typeof(T), @operator); + return first.Subtract(second); } - /// - /// Retrieves the operator associated with the specified type. - /// - /// The type for which to retrieve the operator. - /// The operator associated with the given type. - /// Thrown when no operator exists for the given type. - public static IOperator GetOperator(this Type type) + /// + public override T Multiply(T first, T second) { - if (!_operators.TryGetValue(type, out var @operator)) - { - if (type.Is(typeof(IOperable<>).Make(type))) - { - @operator = typeof(OperableOperator<>).Make(type).CreateInstance(); - _operators.Add(type, @operator); - } - else - throw new InvalidOperationException($"Operator for type {type} doesn't exist."); - } - - return @operator; + return first.Multiply(second); } - /// - /// Retrieves the operator associated with the specified type parameter. - /// - /// The type for which to retrieve the operator. - /// The operator associated with the given type. - public static IOperator GetOperator() + /// + public override T Divide(T first, T second) { - return (IOperator)GetOperator(typeof(T)); + return first.Divide(second); } + } - /// - /// Checks if an operator is registered for the specified type parameter. - /// - /// The type to check for operator registration. - /// true if an operator is registered; otherwise, false. - public static bool IsRegistered() - { - return IsRegistered(typeof(T)); - } + private static readonly SynchronizedDictionary _operators = []; - /// - /// Checks if an operator is registered for the specified type. - /// - /// The type to check for operator registration. - /// true if an operator is registered; otherwise, false. - public static bool IsRegistered(Type type) + /// + /// Initializes static members of the class. + /// Registers the default set of operators. + /// + static OperatorRegistry() + { + AddOperator(new ByteOperator()); + AddOperator(new ShortOperator()); + AddOperator(new IntOperator()); + AddOperator(new LongOperator()); + AddOperator(new FloatOperator()); + AddOperator(new DoubleOperator()); + AddOperator(new DecimalOperator()); + AddOperator(new TimeSpanOperator()); + AddOperator(new DateTimeOperator()); + AddOperator(new DateTimeOffsetOperator()); + AddOperator(new SByteOperator()); + AddOperator(new UShortOperator()); + AddOperator(new UIntOperator()); + AddOperator(new ULongOperator()); + } + + /// + /// Adds an operator for the specified type. + /// + /// The type that the operator will handle. + /// The operator to add. + /// Thrown when the is null. + public static void AddOperator(IOperator @operator) + { + if (@operator is null) + throw new ArgumentNullException(nameof(@operator)); + + _operators.Add(typeof(T), @operator); + } + + /// + /// Retrieves the operator associated with the specified type. + /// + /// The type for which to retrieve the operator. + /// The operator associated with the given type. + /// Thrown when no operator exists for the given type. + public static IOperator GetOperator(this Type type) + { + if (!_operators.TryGetValue(type, out var @operator)) { - return _operators.ContainsKey(type); + if (type.Is(typeof(IOperable<>).Make(type))) + { + @operator = typeof(OperableOperator<>).Make(type).CreateInstance(); + _operators.Add(type, @operator); + } + else + throw new InvalidOperationException($"Operator for type {type} doesn't exist."); } - /// - /// Removes the operator for the specified type. - /// - /// The type whose operator is to be removed. - /// The operator instance to remove. - /// Thrown when the is null. - public static void RemoveOperator(IOperator @operator) - { - if (@operator is null) - throw new ArgumentNullException(nameof(@operator)); + return @operator; + } - _operators.Remove(typeof(T)); - } + /// + /// Retrieves the operator associated with the specified type parameter. + /// + /// The type for which to retrieve the operator. + /// The operator associated with the given type. + public static IOperator GetOperator() + { + return (IOperator)GetOperator(typeof(T)); + } - /// - /// Throws an exception if the specified nullable long value is negative. - /// - /// The nullable long value to check. - /// The name of the variable to include in the exception. - /// The original value if it is not negative; otherwise, null. - public static long? ThrowIfNegative(this long? value, string name) - { - if (value is null) - return null; + /// + /// Checks if an operator is registered for the specified type parameter. + /// + /// The type to check for operator registration. + /// true if an operator is registered; otherwise, false. + public static bool IsRegistered() + { + return IsRegistered(typeof(T)); + } - return value.Value.ThrowIfNegative(name); - } + /// + /// Checks if an operator is registered for the specified type. + /// + /// The type to check for operator registration. + /// true if an operator is registered; otherwise, false. + public static bool IsRegistered(Type type) + { + return _operators.ContainsKey(type); + } - /// - /// Throws an exception if the specified long value is negative. - /// - /// The long value to check. - /// The name of the variable to include in the exception. - /// The original value if it is not negative. - /// Thrown when the value is negative. - public static long ThrowIfNegative(this long value, string name) - { - if (value < 0) - throw new ArgumentOutOfRangeException(name, value, "Invalid value.".Localize()); + /// + /// Removes the operator for the specified type. + /// + /// The type whose operator is to be removed. + /// The operator instance to remove. + /// Thrown when the is null. + public static void RemoveOperator(IOperator @operator) + { + if (@operator is null) + throw new ArgumentNullException(nameof(@operator)); - return value; - } + _operators.Remove(typeof(T)); + } + + /// + /// Throws an exception if the specified nullable long value is negative. + /// + /// The nullable long value to check. + /// The name of the variable to include in the exception. + /// The original value if it is not negative; otherwise, null. + public static long? ThrowIfNegative(this long? value, string name) + { + if (value is null) + return null; + + return value.Value.ThrowIfNegative(name); + } + + /// + /// Throws an exception if the specified long value is negative. + /// + /// The long value to check. + /// The name of the variable to include in the exception. + /// The original value if it is not negative. + /// Thrown when the value is negative. + public static long ThrowIfNegative(this long value, string name) + { + if (value < 0) + throw new ArgumentOutOfRangeException(name, value, "Invalid value.".Localize()); + + return value; } } \ No newline at end of file diff --git a/ComponentModel/PriceExtensions.cs b/ComponentModel/PriceExtensions.cs index f62f53cd..ef95c3fc 100644 --- a/ComponentModel/PriceExtensions.cs +++ b/ComponentModel/PriceExtensions.cs @@ -1,71 +1,70 @@ -namespace Ecng.ComponentModel -{ - using System; +namespace Ecng.ComponentModel; + +using System; - using Ecng.Common; +using Ecng.Common; +/// +/// extension methods. +/// +public static class PriceExtensions +{ /// - /// extension methods. + /// Convert string to . /// - public static class PriceExtensions + /// String value of . + /// Throw if the specified string is empty. + /// Object . + public static Price ToPriceType(this string str, bool throwIfNull = default) { - /// - /// Convert string to . - /// - /// String value of . - /// Throw if the specified string is empty. - /// Object . - public static Price ToPriceType(this string str, bool throwIfNull = default) + if (str.IsEmpty()) { - if (str.IsEmpty()) - { - if (throwIfNull) - throw new ArgumentNullException(nameof(str)); + if (throwIfNull) + throw new ArgumentNullException(nameof(str)); - return null; - } + return null; + } - Price price = new(); + Price price = new(); - var last = str[str.Length - 1]; - - if (!last.IsDigit()) - { - last = last.ToLower(); - str = str.Substring(0, str.Length - 1); + var last = str[str.Length - 1]; + + if (!last.IsDigit()) + { + last = last.ToLower(); + str = str.Substring(0, str.Length - 1); - price.Type = last switch - { - Price.PercentChar => PriceTypes.Percent, - Price.LimitChar => PriceTypes.Limit, - _ => throw new ArgumentOutOfRangeException(nameof(str), $"Unknown unit of measurement '{last}'."), - }; - } + price.Type = last switch + { + Price.PercentChar => PriceTypes.Percent, + Price.LimitChar => PriceTypes.Limit, + _ => throw new ArgumentOutOfRangeException(nameof(str), $"Unknown unit of measurement '{last}'."), + }; + } - price.Value = str.To(); + price.Value = str.To(); - return price; - } + return price; + } - /// - /// Convert the to percents. - /// - /// value. - /// Percents. - public static Price Percents(this int value) => Percents((decimal)value); + /// + /// Convert the to percents. + /// + /// value. + /// Percents. + public static Price Percents(this int value) => Percents((decimal)value); - /// - /// Convert the to percents. - /// - /// value. - /// Percents. - public static Price Percents(this double value) => Percents((decimal)value); + /// + /// Convert the to percents. + /// + /// value. + /// Percents. + public static Price Percents(this double value) => Percents((decimal)value); - /// - /// Convert the to percents. - /// - /// value. - /// Percents. - public static Price Percents(this decimal value) => new(value, PriceTypes.Percent); - } + /// + /// Convert the to percents. + /// + /// value. + /// Percents. + public static Price Percents(this decimal value) => new(value, PriceTypes.Percent); } \ No newline at end of file diff --git a/ComponentModel/Range.cs b/ComponentModel/Range.cs index c53722e9..ab50e262 100644 --- a/ComponentModel/Range.cs +++ b/ComponentModel/Range.cs @@ -1,453 +1,452 @@ -namespace Ecng.ComponentModel -{ - using System; - using System.ComponentModel; +namespace Ecng.ComponentModel; + +using System; +using System.ComponentModel; - using Ecng.Common; +using Ecng.Common; +/// +/// Represents a range with a minimum and maximum value. +/// +public interface IRange +{ /// - /// Represents a range with a minimum and maximum value. + /// Gets a value indicating whether this instance has a minimum value. /// - public interface IRange - { - /// - /// Gets a value indicating whether this instance has a minimum value. - /// - bool HasMinValue { get; } + bool HasMinValue { get; } - /// - /// Gets a value indicating whether this instance has a maximum value. - /// - bool HasMaxValue { get; } + /// + /// Gets a value indicating whether this instance has a maximum value. + /// + bool HasMaxValue { get; } - /// - /// Gets or sets the minimum value of the range. - /// - object Min { get; set; } + /// + /// Gets or sets the minimum value of the range. + /// + object Min { get; set; } - /// - /// Gets or sets the maximum value of the range. - /// - object Max { get; set; } + /// + /// Gets or sets the maximum value of the range. + /// + object Max { get; set; } +} + +/// +/// Represents a generic range defined by a minimum and maximum value. +/// +/// The type of the range values. Must implement IComparable<T>. +[Serializable] +public class Range : Equatable>, IConvertible, IRange + where T : IComparable +{ + /// + /// Initializes static members of the class. + /// + static Range() + { + MinValue = default; + MaxValue = default; } /// - /// Represents a generic range defined by a minimum and maximum value. + /// Initializes a new instance of the class. /// - /// The type of the range values. Must implement IComparable<T>. - [Serializable] - public class Range : Equatable>, IConvertible, IRange - where T : IComparable - { - /// - /// Initializes static members of the class. - /// - static Range() - { - MinValue = default; - MaxValue = default; - } - - /// - /// Initializes a new instance of the class. - /// - public Range() - { - } + public Range() + { + } - /// - /// Initializes a new instance of the class with specified minimum and maximum values. - /// - /// The minimum value of the range. - /// The maximum value of the range. - public Range(T min, T max) - { - Init(min, max); - } + /// + /// Initializes a new instance of the class with specified minimum and maximum values. + /// + /// The minimum value of the range. + /// The maximum value of the range. + public Range(T min, T max) + { + Init(min, max); + } - /// - /// Represents the smallest possible value of type . - /// - public static readonly T MinValue; + /// + /// Represents the smallest possible value of type . + /// + public static readonly T MinValue; - /// - /// Represents the largest possible value of type . - /// - public static readonly T MaxValue; + /// + /// Represents the largest possible value of type . + /// + public static readonly T MaxValue; - #region HasMinValue + #region HasMinValue - /// - /// Gets a value indicating whether the range has a specified minimum value. - /// - [Browsable(false)] - public bool HasMinValue => _min.HasValue; + /// + /// Gets a value indicating whether the range has a specified minimum value. + /// + [Browsable(false)] + public bool HasMinValue => _min.HasValue; - #endregion + #endregion - #region HasMaxValue + #region HasMaxValue - /// - /// Gets a value indicating whether the range has a specified maximum value. - /// - [Browsable(false)] - public bool HasMaxValue => _max.HasValue; + /// + /// Gets a value indicating whether the range has a specified maximum value. + /// + [Browsable(false)] + public bool HasMaxValue => _max.HasValue; - #endregion + #endregion - #region Length + #region Length - private static IOperator _operator; + private static IOperator _operator; - /// - /// Gets the difference between the maximum and minimum values of the range. - /// Returns if either bound is not defined. - /// - [Browsable(false)] - public T Length + /// + /// Gets the difference between the maximum and minimum values of the range. + /// Returns if either bound is not defined. + /// + [Browsable(false)] + public T Length + { + get { - get + if (!HasMinValue || !HasMaxValue) + return MaxValue; + else { - if (!HasMinValue || !HasMaxValue) - return MaxValue; - else - { - if (_operator is null) - _operator = OperatorRegistry.GetOperator(); - - return _operator.Subtract(Max, Min); - } + if (_operator is null) + _operator = OperatorRegistry.GetOperator(); + + return _operator.Subtract(Max, Min); } } + } - #endregion + #endregion - #region Min + #region Min - //private bool _isMinInit; - private readonly NullableEx _min = new(); + //private bool _isMinInit; + private readonly NullableEx _min = new(); - /// - /// Gets or sets the minimum value of the range. - /// - /// - /// Thrown when the new minimum value is greater than the current maximum value. - /// - public T Min + /// + /// Gets or sets the minimum value of the range. + /// + /// + /// Thrown when the new minimum value is greater than the current maximum value. + /// + public T Min + { + get => _min.Value; + set { - get => _min.Value; - set - { - if (_max.HasValue) - ValidateBounds(value, Max); + if (_max.HasValue) + ValidateBounds(value, Max); - _min.Value = value; - //_isMinInit = true; - } + _min.Value = value; + //_isMinInit = true; } + } - #endregion + #endregion - #region Max + #region Max - //private bool _isMaxInit; - private readonly NullableEx _max = new(); + //private bool _isMaxInit; + private readonly NullableEx _max = new(); - /// - /// Gets or sets the maximum value of the range. - /// - /// - /// Thrown when the new maximum value is less than the current minimum value. - /// - public T Max + /// + /// Gets or sets the maximum value of the range. + /// + /// + /// Thrown when the new maximum value is less than the current minimum value. + /// + public T Max + { + get => _max.Value; + set { - get => _max.Value; - set - { - if (_min.HasValue) - ValidateBounds(Min, value); + if (_min.HasValue) + ValidateBounds(Min, value); - _max.Value = value; - //_isMaxInit = true; - } + _max.Value = value; + //_isMaxInit = true; } + } - #endregion + #endregion - #region Parse + #region Parse - /// - /// Defines an explicit conversion of a string representation to a . - /// - /// The string representation of the range. - public static explicit operator Range(string str) - { - return Parse(str); - } + /// + /// Defines an explicit conversion of a string representation to a . + /// + /// The string representation of the range. + public static explicit operator Range(string str) + { + return Parse(str); + } - /// - /// Parses the specified string representation and returns a new instance. - /// - /// The string representation of the range. - /// A new instance of representing the parsed range. - /// Thrown if the input string is empty. - /// Thrown if the string length is less than 3. - public static Range Parse(string value) - { - if (value.IsEmpty()) - throw new ArgumentNullException(nameof(value)); + /// + /// Parses the specified string representation and returns a new instance. + /// + /// The string representation of the range. + /// A new instance of representing the parsed range. + /// Thrown if the input string is empty. + /// Thrown if the string length is less than 3. + public static Range Parse(string value) + { + if (value.IsEmpty()) + throw new ArgumentNullException(nameof(value)); - if (value.Length < 3) - throw new ArgumentOutOfRangeException(nameof(value)); + if (value.Length < 3) + throw new ArgumentOutOfRangeException(nameof(value)); - value = value.Substring(1, value.Length - 2); + value = value.Substring(1, value.Length - 2); - value = value.Remove("Min:"); + value = value.Remove("Min:"); - var part1 = value.Substring(0, value.IndexOf("Max:") - 1); - var part2 = value.Substring(value.IndexOf("Max:") + 4); + var part1 = value.Substring(0, value.IndexOf("Max:") - 1); + var part2 = value.Substring(value.IndexOf("Max:") + 4); - var range = new Range(); + var range = new Range(); - if (!part1.IsEmpty() && part1 != "null") - range.Min = part1.To(); - - if (!part2.IsEmpty() && part2 != "null") - range.Max = part2.To(); - - return range; - } + if (!part1.IsEmpty() && part1 != "null") + range.Min = part1.To(); + + if (!part2.IsEmpty() && part2 != "null") + range.Max = part2.To(); + + return range; + } - #endregion + #endregion - #region Object Members + #region Object Members - /// - public override int GetHashCode() - { - return (HasMinValue ? Min.GetHashCode() : 0) ^ (HasMaxValue ? Max.GetHashCode() : 0); - } + /// + public override int GetHashCode() + { + return (HasMinValue ? Min.GetHashCode() : 0) ^ (HasMaxValue ? Max.GetHashCode() : 0); + } - /// - public override string ToString() - { - return "{{Min:{0} Max:{1}}}".Put(HasMinValue ? Min.ToString() : "null", HasMaxValue ? Max.ToString() : "null"); - } + /// + public override string ToString() + { + return "{{Min:{0} Max:{1}}}".Put(HasMinValue ? Min.ToString() : "null", HasMaxValue ? Max.ToString() : "null"); + } - #endregion + #endregion - #region Equatable> Members + #region Equatable> Members - /// - protected override bool OnEquals(Range other) - { - return _min == other._min && _max == other._max; - } - - #endregion + /// + protected override bool OnEquals(Range other) + { + return _min == other._min && _max == other._max; + } - object IRange.Min - { - get => HasMinValue ? Min : null; - set => Min = (T)value; - } + #endregion - object IRange.Max - { - get => HasMaxValue ? Max : null; - set => Max = (T)value; - } + object IRange.Min + { + get => HasMinValue ? Min : null; + set => Min = (T)value; + } - /// - public override Range Clone() - { - return new Range(Min, Max); - } + object IRange.Max + { + get => HasMaxValue ? Max : null; + set => Max = (T)value; + } - /// - /// Determines whether the current range completely contains another range. - /// - /// The range to check against. - /// true if the range is contained; otherwise, false. - /// Thrown if the provided range is null. - public bool Contains(Range range) - { - if (range is null) - throw new ArgumentNullException(nameof(range)); + /// + public override Range Clone() + { + return new Range(Min, Max); + } - return Contains(range.Min) && Contains(range.Max); - } + /// + /// Determines whether the current range completely contains another range. + /// + /// The range to check against. + /// true if the range is contained; otherwise, false. + /// Thrown if the provided range is null. + public bool Contains(Range range) + { + if (range is null) + throw new ArgumentNullException(nameof(range)); - /// - /// Returns the intersection of the current range with another range. - /// - /// The range with which to intersect. - /// - /// A new representing the overlap between the two ranges, or null if there is no intersection. - /// - /// Thrown if the provided range is null. - public Range Intersect(Range range) - { - if (range is null) - throw new ArgumentNullException(nameof(range)); + return Contains(range.Min) && Contains(range.Max); + } - if (Contains(range)) - return range.Clone(); - else if (range.Contains(this)) - return Clone(); + /// + /// Returns the intersection of the current range with another range. + /// + /// The range with which to intersect. + /// + /// A new representing the overlap between the two ranges, or null if there is no intersection. + /// + /// Thrown if the provided range is null. + public Range Intersect(Range range) + { + if (range is null) + throw new ArgumentNullException(nameof(range)); + + if (Contains(range)) + return range.Clone(); + else if (range.Contains(this)) + return Clone(); + else + { + var containsMin = Contains(range.Min); + var containsMax = Contains(range.Max); + + if (containsMin) + return new Range(range.Min, Max); + else if (containsMax) + return new Range(Min, range.Max); else - { - var containsMin = Contains(range.Min); - var containsMax = Contains(range.Max); - - if (containsMin) - return new Range(range.Min, Max); - else if (containsMax) - return new Range(Min, range.Max); - else - return null; - } + return null; } + } - /// - /// Creates a sub-range from the current range given the specified minimum and maximum values. - /// - /// The minimum value of the sub-range. - /// The maximum value of the sub-range. - /// A new representing the sub-range. - /// Thrown if either min or max is not contained within the current range. - public Range SubRange(T min, T max) - { - if (!Contains(min)) - throw new ArgumentException("Not in range.", nameof(min)); + /// + /// Creates a sub-range from the current range given the specified minimum and maximum values. + /// + /// The minimum value of the sub-range. + /// The maximum value of the sub-range. + /// A new representing the sub-range. + /// Thrown if either min or max is not contained within the current range. + public Range SubRange(T min, T max) + { + if (!Contains(min)) + throw new ArgumentException("Not in range.", nameof(min)); - if (!Contains(max)) - throw new ArgumentException("Not in range.", nameof(max)); + if (!Contains(max)) + throw new ArgumentException("Not in range.", nameof(max)); - return new(min, max); - } + return new(min, max); + } - /// - /// Determines whether the specified value is within the current range. - /// - /// The value to test. - /// true if the value is within the range; otherwise, false. - public bool Contains(T value) - { - if (_min.HasValue && Min.CompareTo(value) > 0) - return false; - else if (_max.HasValue && Max.CompareTo(value) < 0) - return false; - else - return true; - } + /// + /// Determines whether the specified value is within the current range. + /// + /// The value to test. + /// true if the value is within the range; otherwise, false. + public bool Contains(T value) + { + if (_min.HasValue && Min.CompareTo(value) > 0) + return false; + else if (_max.HasValue && Max.CompareTo(value) < 0) + return false; + else + return true; + } - /// - /// Initializes the range with the specified minimum and maximum values. - /// - /// The minimum value. - /// The maximum value. - /// Thrown if the minimum value is greater than the maximum value. - private void Init(T min, T max) - { - ValidateBounds(min, max); + /// + /// Initializes the range with the specified minimum and maximum values. + /// + /// The minimum value. + /// The maximum value. + /// Thrown if the minimum value is greater than the maximum value. + private void Init(T min, T max) + { + ValidateBounds(min, max); - _min.Value = min; - _max.Value = max; - } + _min.Value = min; + _max.Value = max; + } - private static void ValidateBounds(T min, T max) - { - if (min.CompareTo(max) > 0) - throw new ArgumentOutOfRangeException(nameof(min), $"Min value {min} is more than max value {max}."); - } + private static void ValidateBounds(T min, T max) + { + if (min.CompareTo(max) > 0) + throw new ArgumentOutOfRangeException(nameof(min), $"Min value {min} is more than max value {max}."); + } - TypeCode IConvertible.GetTypeCode() - { - return TypeCode.Object; - } + TypeCode IConvertible.GetTypeCode() + { + return TypeCode.Object; + } - bool IConvertible.ToBoolean(IFormatProvider provider) - { - throw new InvalidCastException(); - } + bool IConvertible.ToBoolean(IFormatProvider provider) + { + throw new InvalidCastException(); + } - char IConvertible.ToChar(IFormatProvider provider) - { - throw new InvalidCastException(); - } + char IConvertible.ToChar(IFormatProvider provider) + { + throw new InvalidCastException(); + } - sbyte IConvertible.ToSByte(IFormatProvider provider) - { - throw new InvalidCastException(); - } + sbyte IConvertible.ToSByte(IFormatProvider provider) + { + throw new InvalidCastException(); + } - byte IConvertible.ToByte(IFormatProvider provider) - { - throw new InvalidCastException(); - } + byte IConvertible.ToByte(IFormatProvider provider) + { + throw new InvalidCastException(); + } - short IConvertible.ToInt16(IFormatProvider provider) - { - throw new InvalidCastException(); - } + short IConvertible.ToInt16(IFormatProvider provider) + { + throw new InvalidCastException(); + } - ushort IConvertible.ToUInt16(IFormatProvider provider) - { - throw new InvalidCastException(); - } + ushort IConvertible.ToUInt16(IFormatProvider provider) + { + throw new InvalidCastException(); + } - int IConvertible.ToInt32(IFormatProvider provider) - { - throw new InvalidCastException(); - } + int IConvertible.ToInt32(IFormatProvider provider) + { + throw new InvalidCastException(); + } - uint IConvertible.ToUInt32(IFormatProvider provider) - { - throw new InvalidCastException(); - } + uint IConvertible.ToUInt32(IFormatProvider provider) + { + throw new InvalidCastException(); + } - long IConvertible.ToInt64(IFormatProvider provider) - { - throw new InvalidCastException(); - } + long IConvertible.ToInt64(IFormatProvider provider) + { + throw new InvalidCastException(); + } - ulong IConvertible.ToUInt64(IFormatProvider provider) - { - throw new InvalidCastException(); - } + ulong IConvertible.ToUInt64(IFormatProvider provider) + { + throw new InvalidCastException(); + } - float IConvertible.ToSingle(IFormatProvider provider) - { - throw new InvalidCastException(); - } + float IConvertible.ToSingle(IFormatProvider provider) + { + throw new InvalidCastException(); + } - double IConvertible.ToDouble(IFormatProvider provider) - { - throw new InvalidCastException(); - } + double IConvertible.ToDouble(IFormatProvider provider) + { + throw new InvalidCastException(); + } - decimal IConvertible.ToDecimal(IFormatProvider provider) - { - throw new InvalidCastException(); - } + decimal IConvertible.ToDecimal(IFormatProvider provider) + { + throw new InvalidCastException(); + } - DateTime IConvertible.ToDateTime(IFormatProvider provider) - { - throw new InvalidCastException(); - } + DateTime IConvertible.ToDateTime(IFormatProvider provider) + { + throw new InvalidCastException(); + } - string IConvertible.ToString(IFormatProvider provider) - { - return ToString(); - } + string IConvertible.ToString(IFormatProvider provider) + { + return ToString(); + } - object IConvertible.ToType(Type conversionType, IFormatProvider provider) - { - if (conversionType == typeof(string)) - return ToString(); + object IConvertible.ToType(Type conversionType, IFormatProvider provider) + { + if (conversionType == typeof(string)) + return ToString(); - throw new InvalidCastException(); - } + throw new InvalidCastException(); } } \ No newline at end of file diff --git a/ComponentModel/RangeHelper.cs b/ComponentModel/RangeHelper.cs index 700b0d8d..d6f3d15c 100644 --- a/ComponentModel/RangeHelper.cs +++ b/ComponentModel/RangeHelper.cs @@ -1,230 +1,229 @@ -namespace Ecng.ComponentModel -{ - using System; - using System.Collections.Generic; - using System.Linq; +namespace Ecng.ComponentModel; + +using System; +using System.Collections.Generic; +using System.Linq; - using Ecng.Collections; - using Ecng.Common; - using Ecng.Serialization; +using Ecng.Collections; +using Ecng.Common; +using Ecng.Serialization; +/// +/// Provides helper methods for manipulating Range objects. +/// +public static class RangeHelper +{ /// - /// Provides helper methods for manipulating Range objects. + /// Determines whether the specified range is empty, meaning it has no defined minimum or maximum value. /// - public static class RangeHelper + /// The type of the range values. Must implement IComparable<T>. + /// The range to check. + /// true if the range has no minimum and maximum value; otherwise, false. + /// Thrown when the range is null. + public static bool IsEmpty(this Range range) + where T : IComparable { - /// - /// Determines whether the specified range is empty, meaning it has no defined minimum or maximum value. - /// - /// The type of the range values. Must implement IComparable<T>. - /// The range to check. - /// true if the range has no minimum and maximum value; otherwise, false. - /// Thrown when the range is null. - public static bool IsEmpty(this Range range) - where T : IComparable - { - if (range is null) - throw new ArgumentNullException(nameof(range)); + if (range is null) + throw new ArgumentNullException(nameof(range)); - return !range.HasMaxValue && !range.HasMinValue; - } + return !range.HasMaxValue && !range.HasMinValue; + } - /// - /// Joins overlapping or contiguous ranges in the collection. - /// - /// The type of the range values. Must implement IComparable<T>. - /// The collection of ranges to join. - /// A collection of combined ranges. - public static IEnumerable> JoinRanges(this IEnumerable> ranges) - where T : IComparable - { - var orderedRanges = ranges.OrderBy(r => r.Min).ToList(); + /// + /// Joins overlapping or contiguous ranges in the collection. + /// + /// The type of the range values. Must implement IComparable<T>. + /// The collection of ranges to join. + /// A collection of combined ranges. + public static IEnumerable> JoinRanges(this IEnumerable> ranges) + where T : IComparable + { + var orderedRanges = ranges.OrderBy(r => r.Min).ToList(); - for (var i = 0; i < orderedRanges.Count - 1; ) + for (var i = 0; i < orderedRanges.Count - 1; ) + { + if (orderedRanges[i].Contains(orderedRanges[i + 1])) { - if (orderedRanges[i].Contains(orderedRanges[i + 1])) - { - orderedRanges.RemoveAt(i + 1); - } - else if (orderedRanges[i + 1].Contains(orderedRanges[i])) - { - orderedRanges.RemoveAt(i); - } - else if (orderedRanges[i].Intersect(orderedRanges[i + 1]) != null) - { - orderedRanges[i] = new Range(orderedRanges[i].Min, orderedRanges[i + 1].Max); - orderedRanges.RemoveAt(i + 1); - } - else - i++; + orderedRanges.RemoveAt(i + 1); } - - return orderedRanges; + else if (orderedRanges[i + 1].Contains(orderedRanges[i])) + { + orderedRanges.RemoveAt(i); + } + else if (orderedRanges[i].Intersect(orderedRanges[i + 1]) != null) + { + orderedRanges[i] = new Range(orderedRanges[i].Min, orderedRanges[i + 1].Max); + orderedRanges.RemoveAt(i + 1); + } + else + i++; } - /// - /// Excludes the specified DateTimeOffset range from the current range. - /// - /// The original DateTimeOffset range. - /// The DateTimeOffset range to exclude. - /// A collection of remaining DateTimeOffset ranges after exclusion. - public static IEnumerable> Exclude(this Range from, Range excludingRange) - { - return new Range(from.Min.UtcTicks, from.Max.UtcTicks) - .Exclude(new Range(excludingRange.Min.UtcTicks, excludingRange.Max.UtcTicks)) - .Select(r => new Range(r.Min.To(), r.Max.To())); - } + return orderedRanges; + } - /// - /// Excludes the specified DateTime range from the current range. - /// - /// The original DateTime range. - /// The DateTime range to exclude. - /// A collection of remaining DateTime ranges after exclusion. - public static IEnumerable> Exclude(this Range from, Range excludingRange) - { - return new Range(from.Min.Ticks, from.Max.Ticks) - .Exclude(new Range(excludingRange.Min.Ticks, excludingRange.Max.Ticks)) - .Select(r => new Range(r.Min.To(), r.Max.To())); - } + /// + /// Excludes the specified DateTimeOffset range from the current range. + /// + /// The original DateTimeOffset range. + /// The DateTimeOffset range to exclude. + /// A collection of remaining DateTimeOffset ranges after exclusion. + public static IEnumerable> Exclude(this Range from, Range excludingRange) + { + return new Range(from.Min.UtcTicks, from.Max.UtcTicks) + .Exclude(new Range(excludingRange.Min.UtcTicks, excludingRange.Max.UtcTicks)) + .Select(r => new Range(r.Min.To(), r.Max.To())); + } - /// - /// Excludes the specified long range from the current long range. - /// - /// The original long range. - /// The long range to exclude. - /// A collection of remaining long ranges after exclusion. - public static IEnumerable> Exclude(this Range from, Range excludingRange) + /// + /// Excludes the specified DateTime range from the current range. + /// + /// The original DateTime range. + /// The DateTime range to exclude. + /// A collection of remaining DateTime ranges after exclusion. + public static IEnumerable> Exclude(this Range from, Range excludingRange) + { + return new Range(from.Min.Ticks, from.Max.Ticks) + .Exclude(new Range(excludingRange.Min.Ticks, excludingRange.Max.Ticks)) + .Select(r => new Range(r.Min.To(), r.Max.To())); + } + + /// + /// Excludes the specified long range from the current long range. + /// + /// The original long range. + /// The long range to exclude. + /// A collection of remaining long ranges after exclusion. + public static IEnumerable> Exclude(this Range from, Range excludingRange) + { + var intersectedRange = from.Intersect(excludingRange); + + if (intersectedRange is null) + yield return from; + else { - var intersectedRange = from.Intersect(excludingRange); + if (from == intersectedRange) + yield break; - if (intersectedRange is null) - yield return from; - else + if (from.Contains(intersectedRange)) { - if (from == intersectedRange) - yield break; + if (from.Min != intersectedRange.Min) + yield return new Range(from.Min, intersectedRange.Min - 1); - if (from.Contains(intersectedRange)) - { - if (from.Min != intersectedRange.Min) - yield return new Range(from.Min, intersectedRange.Min - 1); - - if (from.Max != intersectedRange.Max) - yield return new Range(intersectedRange.Max + 1, from.Max); - } + if (from.Max != intersectedRange.Max) + yield return new Range(intersectedRange.Max + 1, from.Max); + } + else + { + if (from.Min < intersectedRange.Min) + yield return new Range(from.Min, intersectedRange.Min); else - { - if (from.Min < intersectedRange.Min) - yield return new Range(from.Min, intersectedRange.Min); - else - yield return new Range(intersectedRange.Max, from.Max); - } + yield return new Range(intersectedRange.Max, from.Max); } } + } - /// - /// Generates ranges from a sequence of DateTimeOffset values within the specified boundaries. - /// - /// The sequence of DateTimeOffset values. - /// The start boundary. - /// The end boundary. - /// A collection of DateTimeOffset ranges. - public static IEnumerable> GetRanges(this IEnumerable dates, DateTimeOffset from, DateTimeOffset to) - { - return dates.Select(d => d.To()) - .GetRanges(from.UtcTicks, to.UtcTicks) - .Select(r => new Range(r.Min.To(), r.Max.To())); - } + /// + /// Generates ranges from a sequence of DateTimeOffset values within the specified boundaries. + /// + /// The sequence of DateTimeOffset values. + /// The start boundary. + /// The end boundary. + /// A collection of DateTimeOffset ranges. + public static IEnumerable> GetRanges(this IEnumerable dates, DateTimeOffset from, DateTimeOffset to) + { + return dates.Select(d => d.To()) + .GetRanges(from.UtcTicks, to.UtcTicks) + .Select(r => new Range(r.Min.To(), r.Max.To())); + } - /// - /// Generates ranges from a sequence of DateTime values within the specified boundaries. - /// - /// The sequence of DateTime values. - /// The start boundary. - /// The end boundary. - /// A collection of DateTime ranges. - public static IEnumerable> GetRanges(this IEnumerable dates, DateTime from, DateTime to) - { - return dates.Select(d => d.To()) - .GetRanges(from.Ticks, to.Ticks) - .Select(r => new Range(r.Min.To(), r.Max.To())); - } + /// + /// Generates ranges from a sequence of DateTime values within the specified boundaries. + /// + /// The sequence of DateTime values. + /// The start boundary. + /// The end boundary. + /// A collection of DateTime ranges. + public static IEnumerable> GetRanges(this IEnumerable dates, DateTime from, DateTime to) + { + return dates.Select(d => d.To()) + .GetRanges(from.Ticks, to.Ticks) + .Select(r => new Range(r.Min.To(), r.Max.To())); + } - /// - /// Generates ranges based on a sequence of long values (ticks) given the specified start and end boundaries. - /// - /// The sequence of long values representing tick counts. - /// The starting tick value. - /// The ending tick value. - /// A collection of ranges represented by long values. - public static IEnumerable> GetRanges(this IEnumerable dates, long from, long to) - { - if (dates.IsEmpty()) - yield break; + /// + /// Generates ranges based on a sequence of long values (ticks) given the specified start and end boundaries. + /// + /// The sequence of long values representing tick counts. + /// The starting tick value. + /// The ending tick value. + /// A collection of ranges represented by long values. + public static IEnumerable> GetRanges(this IEnumerable dates, long from, long to) + { + if (dates.IsEmpty()) + yield break; - const long step = TimeSpan.TicksPerDay; + const long step = TimeSpan.TicksPerDay; - var beginDate = from; - var nextDate = beginDate + step; + var beginDate = from; + var nextDate = beginDate + step; - foreach (var date in dates.Skip(1)) + foreach (var date in dates.Skip(1)) + { + if (date != nextDate) { - if (date != nextDate) - { - yield return new Range(beginDate, nextDate - 1); - beginDate = date; - } - - nextDate = date + step; + yield return new Range(beginDate, nextDate - 1); + beginDate = date; } - yield return new Range(beginDate, nextDate - 1); + nextDate = date + step; } - /// - /// Converts the range to a SettingsStorage object for serialization. - /// - /// The type of the range values. Must implement IComparable<T>. - /// The range to convert. - /// A SettingsStorage object containing the range data. - /// Thrown when the range is null. - public static SettingsStorage ToStorage(this Range range) - where T : IComparable - { - if (range is null) - throw new ArgumentNullException(nameof(range)); + yield return new Range(beginDate, nextDate - 1); + } - return new SettingsStorage() - .Set(nameof(range.Min), range.HasMinValue ? (object)range.Min : null) - .Set(nameof(range.Max), range.HasMaxValue ? (object)range.Max : null); - } + /// + /// Converts the range to a SettingsStorage object for serialization. + /// + /// The type of the range values. Must implement IComparable<T>. + /// The range to convert. + /// A SettingsStorage object containing the range data. + /// Thrown when the range is null. + public static SettingsStorage ToStorage(this Range range) + where T : IComparable + { + if (range is null) + throw new ArgumentNullException(nameof(range)); - /// - /// Constructs a Range<T> object from the provided SettingsStorage. - /// - /// The type of the range values. Must implement IComparable<T>. - /// The SettingsStorage containing the range data. - /// A new Range<T> initialized with the storage values. - /// Thrown when the storage is null. - public static Range ToRange(this SettingsStorage storage) - where T : IComparable - { - if (storage is null) - throw new ArgumentNullException(nameof(storage)); + return new SettingsStorage() + .Set(nameof(range.Min), range.HasMinValue ? (object)range.Min : null) + .Set(nameof(range.Max), range.HasMaxValue ? (object)range.Max : null); + } - var range = new Range(); + /// + /// Constructs a Range<T> object from the provided SettingsStorage. + /// + /// The type of the range values. Must implement IComparable<T>. + /// The SettingsStorage containing the range data. + /// A new Range<T> initialized with the storage values. + /// Thrown when the storage is null. + public static Range ToRange(this SettingsStorage storage) + where T : IComparable + { + if (storage is null) + throw new ArgumentNullException(nameof(storage)); - var min = storage.GetValue(nameof(range.Min)); - var max = storage.GetValue(nameof(range.Max)); + var range = new Range(); - if (min is not null) - range.Min = min.To(); + var min = storage.GetValue(nameof(range.Min)); + var max = storage.GetValue(nameof(range.Max)); - if (max is not null) - range.Max = max.To(); + if (min is not null) + range.Min = min.To(); - return range; - } + if (max is not null) + range.Max = max.To(); + + return range; } } \ No newline at end of file diff --git a/ComponentModel/ServerCredentials.cs b/ComponentModel/ServerCredentials.cs index 3797300f..1e35842e 100644 --- a/ComponentModel/ServerCredentials.cs +++ b/ComponentModel/ServerCredentials.cs @@ -1,97 +1,96 @@ -namespace Ecng.ComponentModel -{ - using System.Security; +namespace Ecng.ComponentModel; + +using System.Security; - using Ecng.Common; - using Ecng.Serialization; +using Ecng.Common; +using Ecng.Serialization; +/// +/// The class that contains a login and password. +/// +public class ServerCredentials : NotifiableObject, IPersistable +{ /// - /// The class that contains a login and password. + /// Initializes a new instance of the . /// - public class ServerCredentials : NotifiableObject, IPersistable + public ServerCredentials() { - /// - /// Initializes a new instance of the . - /// - public ServerCredentials() - { - } + } - private string _email; + private string _email; - /// - /// Email. - /// - public string Email + /// + /// Email. + /// + public string Email + { + get => _email; + set { - get => _email; - set - { - if (_email == value) - return; + if (_email == value) + return; - _email = value; - NotifyChanged(); - } + _email = value; + NotifyChanged(); } + } - private SecureString _password; + private SecureString _password; - /// - /// Password. - /// - public SecureString Password + /// + /// Password. + /// + public SecureString Password + { + get => _password; + set { - get => _password; - set - { - if (_password.IsEqualTo(value)) - return; + if (_password.IsEqualTo(value)) + return; - _password = value; - NotifyChanged(); - } + _password = value; + NotifyChanged(); } + } - private SecureString _token; + private SecureString _token; - /// - /// Token. - /// - public SecureString Token + /// + /// Token. + /// + public SecureString Token + { + get => _token; + set { - get => _token; - set - { - if (_token.IsEqualTo(value)) - return; + if (_token.IsEqualTo(value)) + return; - _token = value; - NotifyChanged(); - } + _token = value; + NotifyChanged(); } + } - /// - /// Load settings. - /// - /// Settings storage. - public virtual void Load(SettingsStorage storage) - { - Email = storage.GetValue(nameof(Email)); - Password = storage.GetValue(nameof(Password)); - Token = storage.GetValue(nameof(Token)); - } + /// + /// Load settings. + /// + /// Settings storage. + public virtual void Load(SettingsStorage storage) + { + Email = storage.GetValue(nameof(Email)); + Password = storage.GetValue(nameof(Password)); + Token = storage.GetValue(nameof(Token)); + } - /// - /// Save settings. - /// - /// Settings storage. - public virtual void Save(SettingsStorage storage) - { - storage - .Set(nameof(Email), Email) - .Set(nameof(Password), Password) - .Set(nameof(Token), Token); - } + /// + /// Save settings. + /// + /// Settings storage. + public virtual void Save(SettingsStorage storage) + { + storage + .Set(nameof(Email), Email) + .Set(nameof(Password), Password) + .Set(nameof(Token), Token); } } \ No newline at end of file diff --git a/ComponentModel/Stat.cs b/ComponentModel/Stat.cs index a1b9027a..105324b3 100644 --- a/ComponentModel/Stat.cs +++ b/ComponentModel/Stat.cs @@ -1,313 +1,312 @@ -namespace Ecng.ComponentModel +namespace Ecng.ComponentModel; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net; +using System.Linq; +using System.Text; + +using Ecng.Common; +using Ecng.Collections; + +/// +/// Contains statistical information related to actions. +/// +/// The type representing the action. +public struct StatInfo { - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Net; - using System.Linq; - using System.Text; - - using Ecng.Common; - using Ecng.Collections; - /// - /// Contains statistical information related to actions. + /// Represents an item that holds a value, an IP address, and an action. /// - /// The type representing the action. - public struct StatInfo + /// The type of the value. + public struct Item { /// - /// Represents an item that holds a value, an IP address, and an action. + /// Gets or sets the value. /// - /// The type of the value. - public struct Item - { - /// - /// Gets or sets the value. - /// - public TValue Value { get; set; } - - /// - /// Gets or sets the IP address. - /// - public IPAddress Address { get; set; } - - /// - /// Gets or sets the action. - /// - public TAction Action { get; set; } - } + public TValue Value { get; set; } /// - /// Gets or sets the unique count. + /// Gets or sets the IP address. /// - public int UniqueCount { get; set; } + public IPAddress Address { get; set; } /// - /// Gets or sets the pending count. + /// Gets or sets the action. /// - public int PendingCount { get; set; } + public TAction Action { get; set; } + } - /// - /// Gets or sets the aggressive IP address. - /// - public IPAddress AggressiveAddress { get; set; } + /// + /// Gets or sets the unique count. + /// + public int UniqueCount { get; set; } - /// - /// Gets or sets the aggressive time. - /// - public TimeSpan AggressiveTime { get; set; } + /// + /// Gets or sets the pending count. + /// + public int PendingCount { get; set; } - /// - /// Gets or sets the frequency information. - /// - public Item[] Freq { get; set; } + /// + /// Gets or sets the aggressive IP address. + /// + public IPAddress AggressiveAddress { get; set; } - /// - /// Gets or sets the longest duration information. - /// - public Item[] Longest { get; set; } + /// + /// Gets or sets the aggressive time. + /// + public TimeSpan AggressiveTime { get; set; } - /// - /// Gets or sets the pending actions information. - /// - public Item[] Pendings { get; set; } + /// + /// Gets or sets the frequency information. + /// + public Item[] Freq { get; set; } - /// - /// Returns a string representation of the statistical information. - /// - /// A string that represents the current stat info. - public override string ToString() - { - var sb = new StringBuilder(); + /// + /// Gets or sets the longest duration information. + /// + public Item[] Longest { get; set; } - sb.AppendLine($"Unique count: {UniqueCount}"); - sb.AppendLine($"Pending count: {PendingCount}"); - sb.AppendLine($"Aggressive IP: {AggressiveAddress}"); - sb.AppendLine($"Aggressive time: {AggressiveTime}"); + /// + /// Gets or sets the pending actions information. + /// + public Item[] Pendings { get; set; } - sb.AppendLine(); + /// + /// Returns a string representation of the statistical information. + /// + /// A string that represents the current stat info. + public override string ToString() + { + var sb = new StringBuilder(); - sb.AppendLine("Freq:"); + sb.AppendLine($"Unique count: {UniqueCount}"); + sb.AppendLine($"Pending count: {PendingCount}"); + sb.AppendLine($"Aggressive IP: {AggressiveAddress}"); + sb.AppendLine($"Aggressive time: {AggressiveTime}"); - foreach (var p in Freq) - sb.AppendLine($"({p.Value}): '{p.Action}'"); + sb.AppendLine(); - sb.AppendLine(); + sb.AppendLine("Freq:"); - sb.AppendLine("Long:"); + foreach (var p in Freq) + sb.AppendLine($"({p.Value}): '{p.Action}'"); - foreach (var p in Longest) - sb.AppendLine($"({p.Value}): '{p.Action}'"); + sb.AppendLine(); - sb.AppendLine(); + sb.AppendLine("Long:"); - sb.AppendLine("Pend:"); + foreach (var p in Longest) + sb.AppendLine($"({p.Value}): '{p.Action}'"); - foreach (var p in Pendings) - sb.AppendLine($"({p.Value}/{p.Address}): '{p.Action}'"); + sb.AppendLine(); - sb.AppendLine(); + sb.AppendLine("Pend:"); - return sb.ToString(); - } + foreach (var p in Pendings) + sb.AppendLine($"({p.Value}/{p.Address}): '{p.Action}'"); + + sb.AppendLine(); + + return sb.ToString(); } +} +/// +/// Provides statistical tracking for actions. +/// +/// The type representing the action. +public class Stat +{ /// - /// Provides statistical tracking for actions. + /// Represents an item that tracks the duration of an action. /// - /// The type representing the action. - public class Stat + public class Item : Disposable { + private readonly Stat _parent; + private readonly Stopwatch _watch; + /// - /// Represents an item that tracks the duration of an action. + /// Initializes a new instance of the class. /// - public class Item : Disposable + /// The parent statistic instance. + /// The action being tracked. + /// The IP address associated with the action. + /// The stopwatch tracking the action duration. + internal Item(Stat parent, TAction action, IPAddress address, Stopwatch watch) { - private readonly Stat _parent; - private readonly Stopwatch _watch; - - /// - /// Initializes a new instance of the class. - /// - /// The parent statistic instance. - /// The action being tracked. - /// The IP address associated with the action. - /// The stopwatch tracking the action duration. - internal Item(Stat parent, TAction action, IPAddress address, Stopwatch watch) - { - _parent = parent ?? throw new ArgumentNullException(nameof(parent)); - Action = action ?? throw new ArgumentNullException(nameof(action)); - Address = address ?? throw new ArgumentNullException(nameof(address)); - _watch = watch ?? throw new ArgumentNullException(nameof(watch)); - } - - /// - /// Gets the action being tracked. - /// - internal readonly TAction Action; - - /// - /// Gets the IP address associated with the action. - /// - internal readonly IPAddress Address; - - /// - /// Disposes the current tracking item and updates the parent statistics. - /// - protected override void DisposeManaged() - { - _watch.Stop(); - _parent.End(this, _watch); - - base.DisposeManaged(); - } + _parent = parent ?? throw new ArgumentNullException(nameof(parent)); + Action = action ?? throw new ArgumentNullException(nameof(action)); + Address = address ?? throw new ArgumentNullException(nameof(address)); + _watch = watch ?? throw new ArgumentNullException(nameof(watch)); } /// - /// Gets or sets the maximum number of longest duration records to keep. + /// Gets the action being tracked. /// - public int LongestLimit { get; set; } = 100; + internal readonly TAction Action; /// - /// Gets or sets the maximum number of frequency records to keep. + /// Gets the IP address associated with the action. /// - public int FreqLimit { get; set; } = 100000; - - private IPAddress _aggressiveIp; - private TimeSpan _aggressiveTime; - private readonly Dictionary _freq = []; - private readonly Collections.PriorityQueue _longests = new((p1, p2) => (p1 - p2).Abs(), new BackwardComparer()); - private readonly Dictionary _pendings = []; - private readonly Dictionary, long, TimeSpan>> _allWatches = []; - private readonly SyncObject _sync = new(); + internal readonly IPAddress Address; /// - /// Retrieves statistical information with support for paging. + /// Disposes the current tracking item and updates the parent statistics. /// - /// The number of records to skip. - /// The number of records to take. - /// A structure containing the current statistics. - public StatInfo GetInfo(int skip, int take) + protected override void DisposeManaged() { - lock (_sync) - { - return new() - { - UniqueCount = _allWatches.Count, - PendingCount = _pendings.Count, - - AggressiveAddress = _aggressiveIp, - AggressiveTime = _aggressiveTime, - - Freq = [.. _freq.OrderByDescending(p => p.Value).Skip(skip).Take(take).Select(p => new StatInfo.Item - { - Value = p.Value, - Action = p.Key, - })], - - Longest = [.. _longests.Skip(skip).Take(take).Select(p => new StatInfo.Item - { - Value = p.Item1, - Action = p.Item2, - })], - - Pendings = [.. _pendings.Skip(skip).Take(take).Select(p => new StatInfo.Item - { - Value = p.Key.Elapsed, - Address = p.Value.Item1, - Action = p.Value.Item2, - })], - }; - } + _watch.Stop(); + _parent.End(this, _watch); + + base.DisposeManaged(); } + } - /// - /// Starts tracking an action. - /// - /// The action to track. - /// The IP address associated with the action. - /// An that represents the action tracking session. - /// Thrown when action or address is null. - public Item Begin(TAction action, IPAddress address) - { - if (action is null) throw new ArgumentNullException(nameof(action)); - if (address is null) throw new ArgumentNullException(nameof(address)); + /// + /// Gets or sets the maximum number of longest duration records to keep. + /// + public int LongestLimit { get; set; } = 100; + + /// + /// Gets or sets the maximum number of frequency records to keep. + /// + public int FreqLimit { get; set; } = 100000; - var watch = Stopwatch.StartNew(); + private IPAddress _aggressiveIp; + private TimeSpan _aggressiveTime; + private readonly Dictionary _freq = []; + private readonly Collections.PriorityQueue _longests = new((p1, p2) => (p1 - p2).Abs(), new BackwardComparer()); + private readonly Dictionary _pendings = []; + private readonly Dictionary, long, TimeSpan>> _allWatches = []; + private readonly SyncObject _sync = new(); - lock (_sync) + /// + /// Retrieves statistical information with support for paging. + /// + /// The number of records to skip. + /// The number of records to take. + /// A structure containing the current statistics. + public StatInfo GetInfo(int skip, int take) + { + lock (_sync) + { + return new() { - _pendings.Add(watch, new(address, action)); - var tuple = _allWatches.SafeAdd(address, key => new([], default, default)); - tuple.First.Add(watch); - tuple.Second++; + UniqueCount = _allWatches.Count, + PendingCount = _pendings.Count, + + AggressiveAddress = _aggressiveIp, + AggressiveTime = _aggressiveTime, - if (_freq.TryGetValue(action, out var counter)) + Freq = [.. _freq.OrderByDescending(p => p.Value).Skip(skip).Take(take).Select(p => new StatInfo.Item { - counter++; - _freq[action] = counter; - } - else + Value = p.Value, + Action = p.Key, + })], + + Longest = [.. _longests.Skip(skip).Take(take).Select(p => new StatInfo.Item { - if (_freq.Count < FreqLimit) - _freq.Add(action, 1); - } - } + Value = p.Item1, + Action = p.Item2, + })], - return new(this, action, address, watch); + Pendings = [.. _pendings.Skip(skip).Take(take).Select(p => new StatInfo.Item + { + Value = p.Key.Elapsed, + Address = p.Value.Item1, + Action = p.Value.Item2, + })], + }; } + } + + /// + /// Starts tracking an action. + /// + /// The action to track. + /// The IP address associated with the action. + /// An that represents the action tracking session. + /// Thrown when action or address is null. + public Item Begin(TAction action, IPAddress address) + { + if (action is null) throw new ArgumentNullException(nameof(action)); + if (address is null) throw new ArgumentNullException(nameof(address)); + + var watch = Stopwatch.StartNew(); - private void End(Item item, Stopwatch watch) + lock (_sync) { - if (item is null) throw new ArgumentNullException(nameof(item)); - if (watch is null) throw new ArgumentNullException(nameof(watch)); + _pendings.Add(watch, new(address, action)); + var tuple = _allWatches.SafeAdd(address, key => new([], default, default)); + tuple.First.Add(watch); + tuple.Second++; + + if (_freq.TryGetValue(action, out var counter)) + { + counter++; + _freq[action] = counter; + } + else + { + if (_freq.Count < FreqLimit) + _freq.Add(action, 1); + } + } + + return new(this, action, address, watch); + } + + private void End(Item item, Stopwatch watch) + { + if (item is null) throw new ArgumentNullException(nameof(item)); + if (watch is null) throw new ArgumentNullException(nameof(watch)); + + var elapsed = watch.Elapsed; + var addr = item.Address; - var elapsed = watch.Elapsed; - var addr = item.Address; + lock (_sync) + { + _pendings.Remove(watch); - lock (_sync) + if (_allWatches.TryGetValue(addr, out var tuple)) { - _pendings.Remove(watch); + tuple.First.Remove(watch); + tuple.Third += elapsed; - if (_allWatches.TryGetValue(addr, out var tuple)) + if (_aggressiveTime == default) { - tuple.First.Remove(watch); - tuple.Third += elapsed; - - if (_aggressiveTime == default) - { - _aggressiveTime = tuple.Third; - _aggressiveIp = addr; - } - else if (_aggressiveTime < tuple.Third) - { - _aggressiveTime = tuple.Third; - _aggressiveIp = addr; - } + _aggressiveTime = tuple.Third; + _aggressiveIp = addr; + } + else if (_aggressiveTime < tuple.Third) + { + _aggressiveTime = tuple.Third; + _aggressiveIp = addr; } - - if (_longests.Count >= LongestLimit) - _longests.EnqueueDequeue(watch.Elapsed, item.Action); - else - _longests.Enqueue(watch.Elapsed, item.Action); } + + if (_longests.Count >= LongestLimit) + _longests.EnqueueDequeue(watch.Elapsed, item.Action); + else + _longests.Enqueue(watch.Elapsed, item.Action); } + } - /// - /// Clears all the recorded statistics. - /// - public void Clear() + /// + /// Clears all the recorded statistics. + /// + public void Clear() + { + lock (_sync) { - lock (_sync) - { - _aggressiveIp = default; - _aggressiveTime = default; - _allWatches.Clear(); - _freq.Clear(); - _pendings.Clear(); - _longests.Clear(); - } + _aggressiveIp = default; + _aggressiveTime = default; + _allWatches.Clear(); + _freq.Clear(); + _pendings.Clear(); + _longests.Clear(); } } } \ No newline at end of file diff --git a/ComponentModel/TimeSpanEditorAttribute.cs b/ComponentModel/TimeSpanEditorAttribute.cs index 3c105d62..f66e8b2d 100644 --- a/ComponentModel/TimeSpanEditorAttribute.cs +++ b/ComponentModel/TimeSpanEditorAttribute.cs @@ -1,57 +1,56 @@ -namespace Ecng.ComponentModel +namespace Ecng.ComponentModel; + +using System; + +/// +/// Show editor parts mask. +/// +[Flags] +public enum TimeSpanEditorMask +{ + /// + /// Days. + /// + Days = 1, + + /// + /// Hours. + /// + Hours = 2, + + /// + /// Minutes. + /// + Minutes = 4, + + /// + /// Seconds. + /// + Seconds = 8, + + /// + /// Milliseconds. + /// + Milliseconds = 16, + + /// + /// Microseconds. + /// + Microseconds = 32, +} + +/// +/// editor attribute. +/// +public class TimeSpanEditorAttribute : Attribute { - using System; - - /// - /// Show editor parts mask. - /// - [Flags] - public enum TimeSpanEditorMask - { - /// - /// Days. - /// - Days = 1, - - /// - /// Hours. - /// - Hours = 2, - - /// - /// Minutes. - /// - Minutes = 4, - - /// - /// Seconds. - /// - Seconds = 8, - - /// - /// Milliseconds. - /// - Milliseconds = 16, - - /// - /// Microseconds. - /// - Microseconds = 32, - } - - /// - /// editor attribute. - /// - public class TimeSpanEditorAttribute : Attribute - { - /// - /// Show editor parts mask. - /// - public TimeSpanEditorMask Mask { get; set; } = DefaultMask; - - /// - /// Default editor parts mask. - /// - public const TimeSpanEditorMask DefaultMask = TimeSpanEditorMask.Hours | TimeSpanEditorMask.Minutes | TimeSpanEditorMask.Seconds; - } + /// + /// Show editor parts mask. + /// + public TimeSpanEditorMask Mask { get; set; } = DefaultMask; + + /// + /// Default editor parts mask. + /// + public const TimeSpanEditorMask DefaultMask = TimeSpanEditorMask.Hours | TimeSpanEditorMask.Minutes | TimeSpanEditorMask.Seconds; } \ No newline at end of file diff --git a/ComponentModel/TypeConverterHelper.cs b/ComponentModel/TypeConverterHelper.cs index 66cce1c9..4a511a10 100644 --- a/ComponentModel/TypeConverterHelper.cs +++ b/ComponentModel/TypeConverterHelper.cs @@ -1,68 +1,67 @@ -namespace Ecng.ComponentModel -{ - #region Using Directives +namespace Ecng.ComponentModel; + +#region Using Directives - using System.ComponentModel; - using System.Globalization; +using System.ComponentModel; +using System.Globalization; - #endregion +#endregion +/// +/// Auxilary class for accessing typed data from type converters. +/// +public static class TypeConverterHelper +{ /// - /// Auxilary class for accessing typed data from type converters. + /// Gets the converter. /// - public static class TypeConverterHelper + /// + public static TypeConverter GetConverter() { - /// - /// Gets the converter. - /// - /// - public static TypeConverter GetConverter() - { - return TypeDescriptor.GetConverter(typeof(T)); - } + return TypeDescriptor.GetConverter(typeof(T)); + } - /// - /// Converts the specified text to an object. - /// - /// The value. - /// - public static T FromString(string value) - { - return (T)GetConverter().ConvertFromString(value); - } + /// + /// Converts the specified text to an object. + /// + /// The value. + /// + public static T FromString(string value) + { + return (T)GetConverter().ConvertFromString(value); + } - /// - /// Converts the specified text to an object. - /// - /// The context. - /// The culture. - /// The value. - /// - public static T FromString(ITypeDescriptorContext context, CultureInfo culture, string value) - { - return (T)GetConverter().ConvertFromString(context, culture, value); - } + /// + /// Converts the specified text to an object. + /// + /// The context. + /// The culture. + /// The value. + /// + public static T FromString(ITypeDescriptorContext context, CultureInfo culture, string value) + { + return (T)GetConverter().ConvertFromString(context, culture, value); + } - /// - /// Converts the specified value to a string representation. - /// - /// The value. - /// - public static string ToString(T value) - { - return GetConverter().ConvertToString(value); - } + /// + /// Converts the specified value to a string representation. + /// + /// The value. + /// + public static string ToString(T value) + { + return GetConverter().ConvertToString(value); + } - /// - /// Converts the specified value to a string representation. - /// - /// The context. - /// The culture. - /// The value. - /// - public static string ToString(ITypeDescriptorContext context, CultureInfo culture, T value) - { - return GetConverter().ConvertToString(context, culture, value); - } + /// + /// Converts the specified value to a string representation. + /// + /// The context. + /// The culture. + /// The value. + /// + public static string ToString(ITypeDescriptorContext context, CultureInfo culture, T value) + { + return GetConverter().ConvertToString(context, culture, value); } } \ No newline at end of file diff --git a/ComponentModel/VectorIconAttribute.cs b/ComponentModel/VectorIconAttribute.cs index 4d425c7e..30247ff0 100644 --- a/ComponentModel/VectorIconAttribute.cs +++ b/ComponentModel/VectorIconAttribute.cs @@ -1,17 +1,16 @@ -namespace Ecng.ComponentModel -{ - using System; +namespace Ecng.ComponentModel; + +using System; - using Ecng.Common; +using Ecng.Common; +/// +/// Represents an attribute that specifies a vector icon. +/// +public class VectorIconAttribute(string icon) : Attribute +{ /// - /// Represents an attribute that specifies a vector icon. + /// Gets the vector icon identifier. /// - public class VectorIconAttribute(string icon) : Attribute - { - /// - /// Gets the vector icon identifier. - /// - public string Icon { get; } = icon.ThrowIfEmpty(nameof(icon)); - } + public string Icon { get; } = icon.ThrowIfEmpty(nameof(icon)); } \ No newline at end of file diff --git a/ComponentModel/ViewModelBase.cs b/ComponentModel/ViewModelBase.cs index 0fea636e..48d0ddbc 100644 --- a/ComponentModel/ViewModelBase.cs +++ b/ComponentModel/ViewModelBase.cs @@ -1,74 +1,73 @@ -namespace Ecng.ComponentModel +namespace Ecng.ComponentModel; + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.ComponentModel; +using System.Linq.Expressions; + +using Ecng.Common; + +/// +/// Базовый класс для реализации View-Model в модели MVVM +/// +public abstract class ViewModelBase : Disposable, INotifyPropertyChanged { - using System; - using System.Collections.Generic; - using System.Runtime.CompilerServices; - using System.ComponentModel; - using System.Linq.Expressions; + /// + public event PropertyChangedEventHandler PropertyChanged; - using Ecng.Common; + /// + /// + protected virtual void OnPropertyChanged([CallerMemberName] string name = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); + } /// - /// Базовый класс для реализации View-Model в модели MVVM /// - public abstract class ViewModelBase : Disposable, INotifyPropertyChanged + protected void OnPropertyChanged(Expression> selectorExpression) { - /// - public event PropertyChangedEventHandler PropertyChanged; + OnPropertyChanged(PropertyName(selectorExpression)); + } - /// - /// - protected virtual void OnPropertyChanged([CallerMemberName] string name = null) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); - } + /// + /// + protected bool SetField(ref T field, T value, Expression> selectorExpression) + { + return SetField(ref field, value, PropertyName(selectorExpression)); + } - /// - /// - protected void OnPropertyChanged(Expression> selectorExpression) - { - OnPropertyChanged(PropertyName(selectorExpression)); - } + /// + /// установка требуемого поля в определенное значение и вызов события PropertyChanged при необходимости + /// + protected virtual bool SetField(ref T field, T value, [CallerMemberName] string name = null) + { + if (EqualityComparer.Default.Equals(field, value)) return false; - /// - /// - protected bool SetField(ref T field, T value, Expression> selectorExpression) - { - return SetField(ref field, value, PropertyName(selectorExpression)); - } + field = value; + OnPropertyChanged(name); - /// - /// установка требуемого поля в определенное значение и вызов события PropertyChanged при необходимости - /// - protected virtual bool SetField(ref T field, T value, [CallerMemberName] string name = null) - { - if (EqualityComparer.Default.Equals(field, value)) return false; + return true; + } + + /// + /// + public static string PropertyName(Expression> property) + { + var lambda = (LambdaExpression)property; - field = value; - OnPropertyChanged(name); + MemberExpression memberExpression; - return true; + if (lambda.Body is UnaryExpression exp) + { + var unaryExpression = exp; + memberExpression = (MemberExpression)unaryExpression.Operand; } - - /// - /// - public static string PropertyName(Expression> property) + else { - var lambda = (LambdaExpression)property; - - MemberExpression memberExpression; - - if (lambda.Body is UnaryExpression exp) - { - var unaryExpression = exp; - memberExpression = (MemberExpression)unaryExpression.Operand; - } - else - { - memberExpression = (MemberExpression)lambda.Body; - } - - return memberExpression.Member.Name; + memberExpression = (MemberExpression)lambda.Body; } + + return memberExpression.Member.Name; } } diff --git a/Configuration/ConfigManager.cs b/Configuration/ConfigManager.cs index f841e57b..49122ad3 100644 --- a/Configuration/ConfigManager.cs +++ b/Configuration/ConfigManager.cs @@ -1,341 +1,340 @@ -namespace Ecng.Configuration -{ - using System; - using System.Collections.Generic; - using System.Collections.Specialized; - using System.Configuration; - using System.Diagnostics; - using System.Linq; +namespace Ecng.Configuration; - using Ecng.Common; +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Configuration; +using System.Diagnostics; +using System.Linq; - /// - /// Provides access to application configuration sections, groups, and services. - /// - public static class ConfigManager - { - private static readonly Dictionary _sections = []; - private static readonly Dictionary _sectionGroups = []; +using Ecng.Common; - private static readonly SyncObject _sync = new(); - private static readonly Dictionary> _services = []; +/// +/// Provides access to application configuration sections, groups, and services. +/// +public static class ConfigManager +{ + private static readonly Dictionary _sections = []; + private static readonly Dictionary _sectionGroups = []; - #region ConfigManager.cctor() + private static readonly SyncObject _sync = new(); + private static readonly Dictionary> _services = []; - static ConfigManager() + #region ConfigManager.cctor() + + static ConfigManager() + { + try { - try - { - //http://csharp-tipsandtricks.blogspot.com/2010/01/identifying-whether-execution-context.html - InnerConfig = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); - } - catch (Exception ex) - { - Trace.WriteLine(ex); - } + //http://csharp-tipsandtricks.blogspot.com/2010/01/identifying-whether-execution-context.html + InnerConfig = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); + } + catch (Exception ex) + { + Trace.WriteLine(ex); + } - if (InnerConfig != null) - { - Trace.WriteLine("ConfigManager FilePath=" + InnerConfig.FilePath); + if (InnerConfig != null) + { + Trace.WriteLine("ConfigManager FilePath=" + InnerConfig.FilePath); - void InitSections(ConfigurationSectionCollection sections) + void InitSections(ConfigurationSectionCollection sections) + { + try { - try + foreach (ConfigurationSection section in sections) { - foreach (ConfigurationSection section in sections) - { - if (!_sections.ContainsKey(section.GetType())) - _sections.Add(section.GetType(), section); - } - } - catch (Exception ex) - { - Trace.WriteLine(ex); + if (!_sections.ContainsKey(section.GetType())) + _sections.Add(section.GetType(), section); } } + catch (Exception ex) + { + Trace.WriteLine(ex); + } + } - void InitSectionGroups(ConfigurationSectionGroupCollection groups) + void InitSectionGroups(ConfigurationSectionGroupCollection groups) + { + foreach (ConfigurationSectionGroup sectionGroup in groups) { - foreach (ConfigurationSectionGroup sectionGroup in groups) - { - if (!_sectionGroups.ContainsKey(sectionGroup.GetType())) - _sectionGroups.Add(sectionGroup.GetType(), sectionGroup); + if (!_sectionGroups.ContainsKey(sectionGroup.GetType())) + _sectionGroups.Add(sectionGroup.GetType(), sectionGroup); - InitSections(sectionGroup.Sections); - InitSectionGroups(sectionGroup.SectionGroups); - } + InitSections(sectionGroup.Sections); + InitSectionGroups(sectionGroup.SectionGroups); } - - InitSections(InnerConfig.Sections); - InitSectionGroups(InnerConfig.SectionGroups); } + + InitSections(InnerConfig.Sections); + InitSectionGroups(InnerConfig.SectionGroups); } + } - #endregion - - /// - /// Gets the underlying configuration file used by the application. - /// - public static Configuration InnerConfig { get; } - - #region GetSection - - /// - /// Returns the configuration section of type T. - /// - /// The type of the configuration section. - /// The configuration section instance of type T. - public static T GetSection() - where T : ConfigurationSection - => (T)GetSection(typeof(T)); - - /// - /// Returns the configuration section matching the specified type, or null if not found. - /// - /// The type of the configuration section. - /// The configuration section instance or null. - public static ConfigurationSection GetSection(Type sectionType) - => _sections.ContainsKey(sectionType) ? _sections[sectionType] : null; - - /// - /// Returns the configuration section of type T specified by the section name. - /// - /// The type of the configuration section. - /// The name of the configuration section. - /// The configuration section instance of type T. - public static T GetSection(string sectionName) - where T : ConfigurationSection - => (T)GetSection(sectionName); - - /// - /// Returns the configuration section specified by the section name. - /// - /// The name of the configuration section. - /// The configuration section instance. - public static ConfigurationSection GetSection(string sectionName) - => InnerConfig.GetSection(sectionName); - - #endregion - - #region GetGroup - - /// - /// Returns the configuration section group of type T. - /// - /// The type of the configuration section group. - /// The configuration section group instance of type T. - public static T GetGroup() - where T : ConfigurationSectionGroup - => (T)GetGroup(typeof(T)); - - /// - /// Returns the configuration section group matching the specified type, or null if not found. - /// - /// The type of the configuration section group. - /// The configuration section group instance or null. - public static ConfigurationSectionGroup GetGroup(Type sectionGroupType) - => _sectionGroups.ContainsKey(sectionGroupType) - ? _sectionGroups[sectionGroupType] - : null; - - /// - /// Returns the configuration section group of type T specified by the section name. - /// - /// The type of the configuration section group. - /// The name of the configuration section group. - /// The configuration section group instance of type T. - public static T GetGroup(string sectionName) - where T : ConfigurationSectionGroup - => (T)GetGroup(sectionName); - - /// - /// Returns the configuration section group specified by the section name. - /// - /// The name of the configuration section group. - /// The configuration section group instance. - public static ConfigurationSectionGroup GetGroup(string sectionName) - => InnerConfig.GetSectionGroup(sectionName); - - #endregion - - /// - /// Tries to get a configuration value from the configuration file using the specified key. - /// - /// The expected value type. - /// The key name. - /// The default value if the key is not found or empty. - /// The configuration value, or the default value if not found. - public static T TryGet(string name, T defaultValue = default) - { - var str = AppSettings.Get(name); + #endregion - return str.IsEmpty() ? defaultValue : str.To(); - } + /// + /// Gets the underlying configuration file used by the application. + /// + public static Configuration InnerConfig { get; } - /// - /// Gets the application settings. - /// - public static NameValueCollection AppSettings => ConfigurationManager.AppSettings; + #region GetSection - /// - /// Occurs when a service is registered. - /// - public static event Action ServiceRegistered; + /// + /// Returns the configuration section of type T. + /// + /// The type of the configuration section. + /// The configuration section instance of type T. + public static T GetSection() + where T : ConfigurationSection + => (T)GetSection(typeof(T)); - private static readonly Dictionary>> _subscribers = new(); + /// + /// Returns the configuration section matching the specified type, or null if not found. + /// + /// The type of the configuration section. + /// The configuration section instance or null. + public static ConfigurationSection GetSection(Type sectionType) + => _sections.ContainsKey(sectionType) ? _sections[sectionType] : null; - /// - /// Subscribes to the service registration event for the specified type. - /// - /// The type of service to subscribe for. - /// The action to invoke when a service is registered. - public static void SubscribeOnRegister(Action registered) - { - if (registered is null) - throw new ArgumentNullException(nameof(registered)); + /// + /// Returns the configuration section of type T specified by the section name. + /// + /// The type of the configuration section. + /// The name of the configuration section. + /// The configuration section instance of type T. + public static T GetSection(string sectionName) + where T : ConfigurationSection + => (T)GetSection(sectionName); - if (!_subscribers.TryGetValue(typeof(T), out var subscribers)) - { - subscribers = []; - _subscribers.Add(typeof(T), subscribers); - } + /// + /// Returns the configuration section specified by the section name. + /// + /// The name of the configuration section. + /// The configuration section instance. + public static ConfigurationSection GetSection(string sectionName) + => InnerConfig.GetSection(sectionName); - subscribers.Add(svc => registered((T)svc)); - } + #endregion - private static void RaiseServiceRegistered(Type type, object service) - { - ServiceRegistered?.Invoke(type, service); + #region GetGroup - if (!_subscribers.TryGetValue(type, out var subscribers)) - return; + /// + /// Returns the configuration section group of type T. + /// + /// The type of the configuration section group. + /// The configuration section group instance of type T. + public static T GetGroup() + where T : ConfigurationSectionGroup + => (T)GetGroup(typeof(T)); - foreach (var subscriber in subscribers) - subscriber(service); - } + /// + /// Returns the configuration section group matching the specified type, or null if not found. + /// + /// The type of the configuration section group. + /// The configuration section group instance or null. + public static ConfigurationSectionGroup GetGroup(Type sectionGroupType) + => _sectionGroups.ContainsKey(sectionGroupType) + ? _sectionGroups[sectionGroupType] + : null; - private static Dictionary GetDict() => GetDict(typeof(T)); + /// + /// Returns the configuration section group of type T specified by the section name. + /// + /// The type of the configuration section group. + /// The name of the configuration section group. + /// The configuration section group instance of type T. + public static T GetGroup(string sectionName) + where T : ConfigurationSectionGroup + => (T)GetGroup(sectionName); - private static Dictionary GetDict(Type type) - { - if (_services.TryGetValue(type, out var dict)) - return dict; + /// + /// Returns the configuration section group specified by the section name. + /// + /// The name of the configuration section group. + /// The configuration section group instance. + public static ConfigurationSectionGroup GetGroup(string sectionName) + => InnerConfig.GetSectionGroup(sectionName); - dict = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - _services.Add(type, dict); - return dict; - } + #endregion - /// - /// Registers the specified service of type T using the default name. - /// - /// The type of the service to register. - /// The service instance. - public static void RegisterService(T service) - => RegisterService(typeof(T).AssemblyQualifiedName, service); - - /// - /// Registers the specified service of type T with the provided name. - /// - /// The type of the service to register. - /// The unique name to register the service. - /// The service instance. - public static void RegisterService(string name, T service) - { - lock (_sync) - GetDict()[name] = service; + /// + /// Tries to get a configuration value from the configuration file using the specified key. + /// + /// The expected value type. + /// The key name. + /// The default value if the key is not found or empty. + /// The configuration value, or the default value if not found. + public static T TryGet(string name, T defaultValue = default) + { + var str = AppSettings.Get(name); - RaiseServiceRegistered(typeof(T), service); - } + return str.IsEmpty() ? defaultValue : str.To(); + } + + /// + /// Gets the application settings. + /// + public static NameValueCollection AppSettings => ConfigurationManager.AppSettings; + + /// + /// Occurs when a service is registered. + /// + public static event Action ServiceRegistered; + + private static readonly Dictionary>> _subscribers = new(); + + /// + /// Subscribes to the service registration event for the specified type. + /// + /// The type of service to subscribe for. + /// The action to invoke when a service is registered. + public static void SubscribeOnRegister(Action registered) + { + if (registered is null) + throw new ArgumentNullException(nameof(registered)); - /// - /// Checks if a service of type T is registered using the default name. - /// - /// The type of the service. - /// True if the service is registered; otherwise, false. - public static bool IsServiceRegistered() - => IsServiceRegistered(typeof(T).AssemblyQualifiedName); - - /// - /// Checks if a service of type T with the provided name is registered. - /// - /// The type of the service. - /// The unique name of the service. - /// True if the service is registered; otherwise, false. - public static bool IsServiceRegistered(string name) + if (!_subscribers.TryGetValue(typeof(T), out var subscribers)) { - lock (_sync) - return GetDict().ContainsKey(name); + subscribers = []; + _subscribers.Add(typeof(T), subscribers); } - /// - /// Tries to retrieve the registered service of type T, returning default if not registered. - /// - /// The type of the service. - /// The service instance, or default if not registered. - public static T TryGetService() - => IsServiceRegistered() ? GetService() : default; - - /// - /// Registers the service of type T if it is not already registered. - /// - /// The type of the service. - /// The service instance. - public static void TryRegisterService(T service) - { - if (IsServiceRegistered()) - return; + subscribers.Add(svc => registered((T)svc)); + } - RegisterService(service); - } + private static void RaiseServiceRegistered(Type type, object service) + { + ServiceRegistered?.Invoke(type, service); - /// - /// Occurs when a service fallback is invoked to construct a service. - /// - public static event Func ServiceFallback; - - /// - /// Retrieves the service of type T by the default name, using fallback if necessary. - /// - /// The type of the service. - /// The service instance. - public static T GetService() - => GetService(typeof(T).AssemblyQualifiedName); - - /// - /// Retrieves the service of type T by specified name, using fallback if necessary. - /// - /// The type of the service. - /// The unique name of the service. - /// The service instance. - public static T GetService(string name) - { - lock (_sync) - { - var dict = GetDict(); + if (!_subscribers.TryGetValue(type, out var subscribers)) + return; - if (dict.TryGetValue(name, out var service)) - return (T)service; - } + foreach (var subscriber in subscribers) + subscriber(service); + } - var fallback = ServiceFallback ?? throw new InvalidOperationException($"Service '{name}' not registered."); + private static Dictionary GetDict() => GetDict(typeof(T)); - var typed = (T)fallback(typeof(T), name) ?? throw new InvalidOperationException($"Service '{name}' not constructed."); + private static Dictionary GetDict(Type type) + { + if (_services.TryGetValue(type, out var dict)) + return dict; - RegisterService(name, typed); + dict = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + _services.Add(type, dict); + return dict; + } - return typed; - } + /// + /// Registers the specified service of type T using the default name. + /// + /// The type of the service to register. + /// The service instance. + public static void RegisterService(T service) + => RegisterService(typeof(T).AssemblyQualifiedName, service); + + /// + /// Registers the specified service of type T with the provided name. + /// + /// The type of the service to register. + /// The unique name to register the service. + /// The service instance. + public static void RegisterService(string name, T service) + { + lock (_sync) + GetDict()[name] = service; + + RaiseServiceRegistered(typeof(T), service); + } - /// - /// Returns all distinct registered services of type T. - /// - /// The type of the services. - /// An enumerable of service instances. - public static IEnumerable GetServices() + /// + /// Checks if a service of type T is registered using the default name. + /// + /// The type of the service. + /// True if the service is registered; otherwise, false. + public static bool IsServiceRegistered() + => IsServiceRegistered(typeof(T).AssemblyQualifiedName); + + /// + /// Checks if a service of type T with the provided name is registered. + /// + /// The type of the service. + /// The unique name of the service. + /// True if the service is registered; otherwise, false. + public static bool IsServiceRegistered(string name) + { + lock (_sync) + return GetDict().ContainsKey(name); + } + + /// + /// Tries to retrieve the registered service of type T, returning default if not registered. + /// + /// The type of the service. + /// The service instance, or default if not registered. + public static T TryGetService() + => IsServiceRegistered() ? GetService() : default; + + /// + /// Registers the service of type T if it is not already registered. + /// + /// The type of the service. + /// The service instance. + public static void TryRegisterService(T service) + { + if (IsServiceRegistered()) + return; + + RegisterService(service); + } + + /// + /// Occurs when a service fallback is invoked to construct a service. + /// + public static event Func ServiceFallback; + + /// + /// Retrieves the service of type T by the default name, using fallback if necessary. + /// + /// The type of the service. + /// The service instance. + public static T GetService() + => GetService(typeof(T).AssemblyQualifiedName); + + /// + /// Retrieves the service of type T by specified name, using fallback if necessary. + /// + /// The type of the service. + /// The unique name of the service. + /// The service instance. + public static T GetService(string name) + { + lock (_sync) { - lock (_sync) - return [.. GetDict().Values.Cast().Distinct()]; + var dict = GetDict(); + + if (dict.TryGetValue(name, out var service)) + return (T)service; } + + var fallback = ServiceFallback ?? throw new InvalidOperationException($"Service '{name}' not registered."); + + var typed = (T)fallback(typeof(T), name) ?? throw new InvalidOperationException($"Service '{name}' not constructed."); + + RegisterService(name, typed); + + return typed; + } + + /// + /// Returns all distinct registered services of type T. + /// + /// The type of the services. + /// An enumerable of service instances. + public static IEnumerable GetServices() + { + lock (_sync) + return [.. GetDict().Values.Cast().Distinct()]; } } \ No newline at end of file diff --git a/Data.Linq2db/DataHelper.cs b/Data.Linq2db/DataHelper.cs index dafb4852..6ca0b157 100644 --- a/Data.Linq2db/DataHelper.cs +++ b/Data.Linq2db/DataHelper.cs @@ -1,37 +1,36 @@ -namespace Ecng.Data -{ - using System; +namespace Ecng.Data; + +using System; - using Ecng.Common; +using Ecng.Common; - using LinqToDB.Data; +using LinqToDB.Data; +/// +/// Extensions for . +/// +public static class DataHelper +{ /// - /// Extensions for . + /// Create connection. /// - public static class DataHelper + /// . + /// . + public static DataConnection CreateConnection(this DatabaseConnectionPair pair) { - /// - /// Create connection. - /// - /// . - /// . - public static DataConnection CreateConnection(this DatabaseConnectionPair pair) - { - if (pair is null) - throw new ArgumentNullException(nameof(pair)); + if (pair is null) + throw new ArgumentNullException(nameof(pair)); - var provider = pair.Provider; + var provider = pair.Provider; - if (provider.IsEmpty()) - throw new InvalidOperationException("Provider is not set."); + if (provider.IsEmpty()) + throw new InvalidOperationException("Provider is not set."); - var connStr = pair.ConnectionString; + var connStr = pair.ConnectionString; - if (connStr.IsEmpty()) - throw new InvalidOperationException("Cannot create a connection, because some data was not entered."); + if (connStr.IsEmpty()) + throw new InvalidOperationException("Cannot create a connection, because some data was not entered."); - return new DataConnection(provider, connStr); - } + return new DataConnection(provider, connStr); } } \ No newline at end of file diff --git a/Data/DatabaseConnectionCache.cs b/Data/DatabaseConnectionCache.cs index 972a56d6..ceba7755 100644 --- a/Data/DatabaseConnectionCache.cs +++ b/Data/DatabaseConnectionCache.cs @@ -1,117 +1,116 @@ -namespace Ecng.Data +namespace Ecng.Data; + +using System; +using System.Collections.Generic; +using System.Linq; + +using Ecng.Collections; +using Ecng.Common; +using Ecng.Serialization; + +/// +/// Represents a cache for objects. +/// +public class DatabaseConnectionCache : IPersistable { - using System; - using System.Collections.Generic; - using System.Linq; + private readonly CachedSynchronizedSet _connections = new(); + + /// + /// Gets all database connection pairs. + /// + public IEnumerable Connections => _connections.Cache; + + /// + /// Occurs when a new database connection pair is created. + /// + public event Action ConnectionCreated; + + /// + /// Occurs when a database connection pair is deleted. + /// + public event Action ConnectionDeleted; - using Ecng.Collections; - using Ecng.Common; - using Ecng.Serialization; + /// + /// Occurs when the connection cache is updated. + /// + public event Action Updated; /// - /// Represents a cache for objects. + /// Retrieves an existing connection matching the specified provider and connection string or adds a new connection if it does not exist. /// - public class DatabaseConnectionCache : IPersistable + /// The database provider. + /// The connection string. + /// The corresponding . + public DatabaseConnectionPair GetOrAdd(string provider, string connectionString) { - private readonly CachedSynchronizedSet _connections = new(); - - /// - /// Gets all database connection pairs. - /// - public IEnumerable Connections => _connections.Cache; - - /// - /// Occurs when a new database connection pair is created. - /// - public event Action ConnectionCreated; - - /// - /// Occurs when a database connection pair is deleted. - /// - public event Action ConnectionDeleted; - - /// - /// Occurs when the connection cache is updated. - /// - public event Action Updated; - - /// - /// Retrieves an existing connection matching the specified provider and connection string or adds a new connection if it does not exist. - /// - /// The database provider. - /// The connection string. - /// The corresponding . - public DatabaseConnectionPair GetOrAdd(string provider, string connectionString) - { - if (provider.IsEmpty()) - throw new ArgumentNullException(nameof(provider)); + if (provider.IsEmpty()) + throw new ArgumentNullException(nameof(provider)); - if (connectionString.IsEmpty()) - throw new ArgumentNullException(nameof(connectionString)); + if (connectionString.IsEmpty()) + throw new ArgumentNullException(nameof(connectionString)); - var isNew = false; - DatabaseConnectionPair connection; + var isNew = false; + DatabaseConnectionPair connection; - lock (_connections.SyncRoot) - { - connection = _connections.FirstOrDefault(p => p.Provider == provider && p.ConnectionString.EqualsIgnoreCase(connectionString)); + lock (_connections.SyncRoot) + { + connection = _connections.FirstOrDefault(p => p.Provider == provider && p.ConnectionString.EqualsIgnoreCase(connectionString)); - if (connection is null) + if (connection is null) + { + isNew = true; + _connections.Add(connection = new() { - isNew = true; - _connections.Add(connection = new() - { - Provider = provider, - ConnectionString = connectionString, - }); - } + Provider = provider, + ConnectionString = connectionString, + }); } + } - if (isNew) - ConnectionCreated?.Invoke(connection); + if (isNew) + ConnectionCreated?.Invoke(connection); - Updated?.Invoke(); - return connection; - } + Updated?.Invoke(); + return connection; + } - /// - /// Deletes the specified database connection pair from the cache. - /// - /// The database connection pair to delete. - /// - /// True if the connection was successfully removed; otherwise, false. - /// - public bool DeleteConnection(DatabaseConnectionPair connection) - { - if (connection is null) - throw new ArgumentNullException(nameof(connection)); + /// + /// Deletes the specified database connection pair from the cache. + /// + /// The database connection pair to delete. + /// + /// True if the connection was successfully removed; otherwise, false. + /// + public bool DeleteConnection(DatabaseConnectionPair connection) + { + if (connection is null) + throw new ArgumentNullException(nameof(connection)); - if (!_connections.Remove(connection)) - return false; + if (!_connections.Remove(connection)) + return false; - ConnectionDeleted?.Invoke(connection); - Updated?.Invoke(); - return true; - } + ConnectionDeleted?.Invoke(connection); + Updated?.Invoke(); + return true; + } - /// - /// Loads the database connection pairs from the specified settings storage. - /// - /// The settings storage to load from. - public void Load(SettingsStorage storage) - { - _connections.AddRange(storage - .GetValue>(nameof(Connections)) - .Where(p => p.Provider != null)); - } + /// + /// Loads the database connection pairs from the specified settings storage. + /// + /// The settings storage to load from. + public void Load(SettingsStorage storage) + { + _connections.AddRange(storage + .GetValue>(nameof(Connections)) + .Where(p => p.Provider != null)); + } - /// - /// Saves the database connection pairs to the specified settings storage. - /// - /// The settings storage to save to. - public void Save(SettingsStorage storage) - { - storage.SetValue(nameof(Connections), Connections.Select(pair => pair.Save()).ToArray()); - } + /// + /// Saves the database connection pairs to the specified settings storage. + /// + /// The settings storage to save to. + public void Save(SettingsStorage storage) + { + storage.SetValue(nameof(Connections), Connections.Select(pair => pair.Save()).ToArray()); } } \ No newline at end of file diff --git a/Data/DatabaseConnectionPair.cs b/Data/DatabaseConnectionPair.cs index 28cb6ccd..4f6da509 100644 --- a/Data/DatabaseConnectionPair.cs +++ b/Data/DatabaseConnectionPair.cs @@ -1,68 +1,67 @@ -namespace Ecng.Data +namespace Ecng.Data; + +using Ecng.ComponentModel; +using Ecng.Serialization; + +/// +/// Provider and connection string pair. +/// +public class DatabaseConnectionPair : NotifiableObject, IPersistable { - using Ecng.ComponentModel; - using Ecng.Serialization; + private string _provider; /// - /// Provider and connection string pair. + /// Provider type. /// - public class DatabaseConnectionPair : NotifiableObject, IPersistable + public string Provider { - private string _provider; - - /// - /// Provider type. - /// - public string Provider + get => _provider; + set { - get => _provider; - set - { - _provider = value; - UpdateTitle(); - } + _provider = value; + UpdateTitle(); } + } - private string _connectionString; + private string _connectionString; - /// - /// Connection settings. - /// - public string ConnectionString + /// + /// Connection settings. + /// + public string ConnectionString + { + get => _connectionString; + set { - get => _connectionString; - set - { - _connectionString = value; - UpdateTitle(); - } + _connectionString = value; + UpdateTitle(); } + } - /// - /// Connection title. - /// - public string Title => $"({Provider}) {ConnectionString}"; + /// + /// Connection title. + /// + public string Title => $"({Provider}) {ConnectionString}"; - private void UpdateTitle() - { - NotifyChanged(nameof(Title)); - } + private void UpdateTitle() + { + NotifyChanged(nameof(Title)); + } - /// - public override string ToString() => Title; + /// + public override string ToString() => Title; - void IPersistable.Load(SettingsStorage storage) - { - Provider = storage.GetValue(nameof(Provider)); - ConnectionString = storage.GetValue(nameof(ConnectionString)); - } + void IPersistable.Load(SettingsStorage storage) + { + Provider = storage.GetValue(nameof(Provider)); + ConnectionString = storage.GetValue(nameof(ConnectionString)); + } - void IPersistable.Save(SettingsStorage storage) - { - storage - .Set(nameof(Provider), Provider) - .Set(nameof(ConnectionString), ConnectionString) - ; - } + void IPersistable.Save(SettingsStorage storage) + { + storage + .Set(nameof(Provider), Provider) + .Set(nameof(ConnectionString), ConnectionString) + ; } } \ No newline at end of file diff --git a/IO/CompressionHelper.cs b/IO/CompressionHelper.cs index 35c0c5aa..3a8c1711 100644 --- a/IO/CompressionHelper.cs +++ b/IO/CompressionHelper.cs @@ -1,331 +1,330 @@ -namespace Ecng.IO -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.IO.Compression; - using System.Threading; - using System.Threading.Tasks; +namespace Ecng.IO; + +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Threading; +using System.Threading.Tasks; - using Ecng.Common; +using Ecng.Common; - using Nito.AsyncEx; +using Nito.AsyncEx; +/// +/// Provides helper methods for compressing and decompressing data using various algorithms. +/// +public static class CompressionHelper +{ /// - /// Provides helper methods for compressing and decompressing data using various algorithms. + /// Extracts entries from a ZIP archive contained in the specified byte array. /// - public static class CompressionHelper + /// The byte array containing the ZIP archive. + /// Whether to leave the underlying stream open. + /// A function to filter entries by name. Only entries returning true will be processed. + /// An enumerable of tuples with the entry name and its content stream. + public static IEnumerable<(string name, Stream body)> Unzip(this byte[] input, bool leaveOpen = false, Func filter = null) { - /// - /// Extracts entries from a ZIP archive contained in the specified byte array. - /// - /// The byte array containing the ZIP archive. - /// Whether to leave the underlying stream open. - /// A function to filter entries by name. Only entries returning true will be processed. - /// An enumerable of tuples with the entry name and its content stream. - public static IEnumerable<(string name, Stream body)> Unzip(this byte[] input, bool leaveOpen = false, Func filter = null) - { - return input.To().Unzip(leaveOpen, filter); - } + return input.To().Unzip(leaveOpen, filter); + } - /// - /// Extracts entries from a ZIP archive contained in the specified stream. - /// - /// The stream containing the ZIP archive. - /// Whether to leave the underlying stream open after processing. - /// A function to filter entries by name. Only entries returning true will be processed. - /// An enumerable of tuples with the entry full name and its content stream. - public static IEnumerable<(string name, Stream body)> Unzip(this Stream input, bool leaveOpen = false, Func filter = null) + /// + /// Extracts entries from a ZIP archive contained in the specified stream. + /// + /// The stream containing the ZIP archive. + /// Whether to leave the underlying stream open after processing. + /// A function to filter entries by name. Only entries returning true will be processed. + /// An enumerable of tuples with the entry full name and its content stream. + public static IEnumerable<(string name, Stream body)> Unzip(this Stream input, bool leaveOpen = false, Func filter = null) + { + using (var zip = new ZipArchive(input, ZipArchiveMode.Read, leaveOpen)) { - using (var zip = new ZipArchive(input, ZipArchiveMode.Read, leaveOpen)) + foreach (var entry in zip.Entries) { - foreach (var entry in zip.Entries) - { - if (filter?.Invoke(entry.Name) == false) - continue; - - using var stream = entry.Open(); - yield return (entry.FullName, stream); - } - } + if (filter?.Invoke(entry.Name) == false) + continue; - if (!leaveOpen) - input.Close(); + using var stream = entry.Open(); + yield return (entry.FullName, stream); + } } - /// - /// Represents the default buffer size used in compression and decompression operations. - /// - public const int DefaultBufferSize = FileSizes.KB * 80; - - /// - /// Decompresses a GZip-compressed byte array into a UTF-8 encoded string. - /// - /// The byte array containing GZip-compressed data. - /// A string resulting from decompression. - public static string UnGZip(this byte[] input) - => input.UnGZip(0, input.Length); - - /// - /// Decompresses a GZip-compressed segment of bytes into a UTF-8 encoded string. - /// - /// The byte array segment containing GZip-compressed data. - /// A string resulting from decompression. - public static string UnGZip(this ArraySegment v) - => v.Array.UnGZip(v.Offset, v.Count); - - /// - /// Decompresses a GZip-compressed portion of a byte array into a UTF-8 encoded string. - /// - /// The byte array containing GZip-compressed data. - /// The starting index from which to begin decompression. - /// The number of bytes to decompress. - /// A string resulting from decompression. - public static string UnGZip(this byte[] input, int index, int count) - => input.Uncompress(index, count).UTF8(); - - /// - /// Decompresses a GZip-compressed segment of bytes into the provided destination buffer. - /// - /// The byte array segment containing GZip-compressed data. - /// The buffer to store decompressed data. - /// The number of bytes written into the destination buffer. - public static int UnGZip(this ArraySegment input, byte[] destination) - => UnGZip(input.Array, input.Offset, input.Count, destination); - - /// - /// Decompresses a portion of a GZip-compressed byte array into the provided destination buffer. - /// - /// The byte array containing GZip-compressed data. - /// The starting index for decompression. - /// The number of bytes to decompress. - /// The buffer to store decompressed data. - /// The number of bytes written into the destination buffer. - public static int UnGZip(this byte[] input, int index, int count, byte[] destination) - { - using var inputStream = new MemoryStream(input, index, count); - using var outputStream = new MemoryStream(destination); - AsyncContext.Run(() => inputStream.UncompressAsync(outputStream, true)); - return (int)outputStream.Position; - } + if (!leaveOpen) + input.Close(); + } - /// - /// Decompresses a Deflate-compressed byte array into a UTF-8 encoded string. - /// - /// The byte array containing Deflate-compressed data. - /// A string resulting from decompression. - public static string UnDeflate(this byte[] input) - => input.UnDeflate(0, input.Length); - - /// - /// Decompresses a segment of Deflate-compressed bytes into a UTF-8 encoded string. - /// - /// The byte array segment containing Deflate-compressed data. - /// A string resulting from decompression. - public static string UnDeflate(this ArraySegment v) - => v.Array.UnDeflate(v.Offset, v.Count); - - /// - /// Decompresses a portion of a Deflate-compressed byte array into a UTF-8 encoded string. - /// - /// The byte array containing Deflate-compressed data. - /// The starting index for decompression. - /// The number of bytes to decompress. - /// A string resulting from decompression. - public static string UnDeflate(this byte[] input, int index, int count) - => input.DeflateFrom(index, count).UTF8(); - - /// - /// Decompresses a Deflate-compressed segment of bytes into the provided destination buffer. - /// - /// The byte array segment containing Deflate-compressed data. - /// The buffer to store decompressed data. - /// The number of bytes written into the destination buffer. - public static int UnDeflate(this ArraySegment input, byte[] destination) - => UnDeflate(input.Array, input.Offset, input.Count, destination); - - /// - /// Decompresses a portion of a Deflate-compressed byte array into the provided destination buffer. - /// - /// The byte array containing Deflate-compressed data. - /// The starting index for decompression. - /// The number of bytes to decompress. - /// The buffer to store decompressed data. - /// The number of bytes written into the destination buffer. - public static int UnDeflate(this byte[] input, int index, int count, byte[] destination) - { - using var inputStream = new MemoryStream(input, index, count); - using var outputStream = new MemoryStream(destination); - AsyncContext.Run(() => inputStream.UncompressAsync(outputStream, true)); - return (int)outputStream.Position; - } + /// + /// Represents the default buffer size used in compression and decompression operations. + /// + public const int DefaultBufferSize = FileSizes.KB * 80; - /// - /// Compresses the specified byte array using the Deflate algorithm. - /// - /// The byte array to compress. - /// A compressed byte array. - public static byte[] DeflateTo(this byte[] input) - => input.Compress(); - - /// - /// Compresses a segment of a byte array using the Deflate algorithm. - /// - /// The byte array segment to compress. - /// A compressed byte array. - public static byte[] DeflateFrom(this ArraySegment v) - => v.Array.DeflateFrom(v.Offset, v.Count); - - /// - /// Compresses a portion of a byte array using the Deflate algorithm. - /// - /// The byte array to compress. - /// The starting index from which to begin compression. - /// The number of bytes to compress. - /// The buffer size to use during compression. - /// A compressed byte array. - public static byte[] DeflateFrom(this byte[] input, int? index = default, int? count = default, int bufferSize = DefaultBufferSize) - => input.Uncompress(index, count, bufferSize); - - /// - /// Compresses the specified byte array using the 7Zip (LZMA) algorithm. - /// - /// The byte array to compress. - /// A compressed byte array. - public static byte[] Do7Zip(this byte[] input) - => input.Compress(); - - /// - /// Decompresses a 7Zip (LZMA) compressed byte array. - /// - /// The byte array containing 7Zip-compressed data. - /// A decompressed byte array. - public static byte[] Un7Zip(this byte[] input) - => input.Uncompress(); - - /// - /// Compresses a portion of a byte array using the specified compression stream. - /// - /// The type of compression stream to use. - /// The byte array to compress. - /// The starting index for compression. - /// The number of bytes to compress. - /// The compression level to use. - /// The buffer size to use during compression. - /// A compressed byte array. - public static byte[] Compress(this byte[] input, int? index = default, int? count = default, CompressionLevel level = CompressionLevel.Optimal, int bufferSize = DefaultBufferSize) - where TCompressStream : Stream - => AsyncContext.Run(() => input.CompressAsync(index, count, level, bufferSize)); - - /// - /// Decompresses a portion of a byte array using the specified compression stream. - /// - /// The type of compression stream to use. - /// The byte array containing compressed data. - /// The starting index for decompression. - /// The number of bytes to decompress. - /// The buffer size to use during decompression. - /// A decompressed byte array. - public static byte[] Uncompress(this byte[] input, int? index = default, int? count = default, int bufferSize = DefaultBufferSize) - where TCompressStream : Stream - => AsyncContext.Run(() => input.UncompressAsync(index, count, bufferSize)); - - /// - /// Asynchronously compresses a segment of a byte array using the specified compression stream. - /// - /// The type of compression stream to use. - /// The segment of the byte array to compress. - /// The compression level to use. - /// The buffer size to use during compression. - /// A task representing the asynchronous operation, with a compressed byte array as the result. - public static Task CompressAsync(this ArraySegment v, CompressionLevel level = CompressionLevel.Optimal, int bufferSize = DefaultBufferSize) - where TCompressStream : Stream - => v.Array.CompressAsync(v.Offset, v.Count, level, bufferSize); - - /// - /// Asynchronously compresses a portion of a byte array using the specified compression stream. - /// - /// The type of compression stream to use. - /// The byte array to compress. - /// The starting index for compression. - /// The number of bytes to compress. - /// The compression level to use. - /// The buffer size to use during compression. - /// A token to monitor for cancellation requests. - /// A task representing the asynchronous operation, with a compressed byte array as the result. - public static async Task CompressAsync(this byte[] input, int? index = default, int? count = default, CompressionLevel level = CompressionLevel.Optimal, int bufferSize = DefaultBufferSize, CancellationToken cancellationToken = default) - where TCompressStream : Stream - { - if (input is null) - throw new ArgumentNullException(nameof(input)); + /// + /// Decompresses a GZip-compressed byte array into a UTF-8 encoded string. + /// + /// The byte array containing GZip-compressed data. + /// A string resulting from decompression. + public static string UnGZip(this byte[] input) + => input.UnGZip(0, input.Length); - using var inputStream = new MemoryStream(input, index ?? 0, count ?? input.Length); - using var outputStream = new MemoryStream(); - await inputStream.CompressAsync(outputStream, level, true, bufferSize, cancellationToken); - return outputStream.To(); - } + /// + /// Decompresses a GZip-compressed segment of bytes into a UTF-8 encoded string. + /// + /// The byte array segment containing GZip-compressed data. + /// A string resulting from decompression. + public static string UnGZip(this ArraySegment v) + => v.Array.UnGZip(v.Offset, v.Count); - /// - /// Asynchronously decompresses a portion of a byte array using the specified compression stream. - /// - /// The type of compression stream to use. - /// The byte array containing compressed data. - /// The starting index for decompression. - /// The number of bytes to decompress. - /// The buffer size to use during decompression. - /// A token to monitor for cancellation requests. - /// A task representing the asynchronous operation, with a decompressed byte array as the result. - public static async Task UncompressAsync(this byte[] input, int? index = default, int? count = default, int bufferSize = DefaultBufferSize, CancellationToken cancellationToken = default) - where TCompressStream : Stream - { - if (input is null) - throw new ArgumentNullException(nameof(input)); + /// + /// Decompresses a GZip-compressed portion of a byte array into a UTF-8 encoded string. + /// + /// The byte array containing GZip-compressed data. + /// The starting index from which to begin decompression. + /// The number of bytes to decompress. + /// A string resulting from decompression. + public static string UnGZip(this byte[] input, int index, int count) + => input.Uncompress(index, count).UTF8(); - using var inputStream = new MemoryStream(input, index ?? 0, count ?? input.Length); - using var outputStream = new MemoryStream(); - await inputStream.UncompressAsync(outputStream, true, bufferSize, cancellationToken); - return outputStream.To(); - } + /// + /// Decompresses a GZip-compressed segment of bytes into the provided destination buffer. + /// + /// The byte array segment containing GZip-compressed data. + /// The buffer to store decompressed data. + /// The number of bytes written into the destination buffer. + public static int UnGZip(this ArraySegment input, byte[] destination) + => UnGZip(input.Array, input.Offset, input.Count, destination); - /// - /// Asynchronously compresses data from the input stream and writes the compressed data to the output stream using the specified compression stream. - /// - /// The type of compression stream to use. - /// The input stream containing data to compress. - /// The output stream to write compressed data to. - /// The compression level to use. - /// Whether to leave the output stream open after compression. - /// The buffer size to use during compression. - /// A token to monitor for cancellation requests. - /// A task representing the asynchronous compression operation. - public static async Task CompressAsync(this Stream input, Stream output, CompressionLevel level = CompressionLevel.Optimal, bool leaveOpen = true, int bufferSize = DefaultBufferSize, CancellationToken cancellationToken = default) - where TCompressStream : Stream - { - if (input is null) - throw new ArgumentNullException(nameof(input)); + /// + /// Decompresses a portion of a GZip-compressed byte array into the provided destination buffer. + /// + /// The byte array containing GZip-compressed data. + /// The starting index for decompression. + /// The number of bytes to decompress. + /// The buffer to store decompressed data. + /// The number of bytes written into the destination buffer. + public static int UnGZip(this byte[] input, int index, int count, byte[] destination) + { + using var inputStream = new MemoryStream(input, index, count); + using var outputStream = new MemoryStream(destination); + AsyncContext.Run(() => inputStream.UncompressAsync(outputStream, true)); + return (int)outputStream.Position; + } - using var compress = (TCompressStream)Activator.CreateInstance(typeof(TCompressStream), output, level, leaveOpen); - await input.CopyToAsync(compress, bufferSize, cancellationToken); - } + /// + /// Decompresses a Deflate-compressed byte array into a UTF-8 encoded string. + /// + /// The byte array containing Deflate-compressed data. + /// A string resulting from decompression. + public static string UnDeflate(this byte[] input) + => input.UnDeflate(0, input.Length); - /// - /// Asynchronously decompresses data from the input stream and writes the decompressed data to the output stream using the specified compression stream. - /// - /// The type of compression stream to use. - /// The input stream containing compressed data. - /// The output stream to write decompressed data to. - /// Whether to leave the input stream open after decompression. - /// The buffer size to use during decompression. - /// A token to monitor for cancellation requests. - /// A task representing the asynchronous decompression operation. - public static async Task UncompressAsync(this Stream input, Stream output, bool leaveOpen = true, int bufferSize = DefaultBufferSize, CancellationToken cancellationToken = default) - where TCompressStream : Stream - { - if (input is null) - throw new ArgumentNullException(nameof(input)); + /// + /// Decompresses a segment of Deflate-compressed bytes into a UTF-8 encoded string. + /// + /// The byte array segment containing Deflate-compressed data. + /// A string resulting from decompression. + public static string UnDeflate(this ArraySegment v) + => v.Array.UnDeflate(v.Offset, v.Count); - using var compress = (TCompressStream)Activator.CreateInstance(typeof(TCompressStream), input, CompressionMode.Decompress, leaveOpen); - await compress.CopyToAsync(output, bufferSize, cancellationToken); - } + /// + /// Decompresses a portion of a Deflate-compressed byte array into a UTF-8 encoded string. + /// + /// The byte array containing Deflate-compressed data. + /// The starting index for decompression. + /// The number of bytes to decompress. + /// A string resulting from decompression. + public static string UnDeflate(this byte[] input, int index, int count) + => input.DeflateFrom(index, count).UTF8(); + + /// + /// Decompresses a Deflate-compressed segment of bytes into the provided destination buffer. + /// + /// The byte array segment containing Deflate-compressed data. + /// The buffer to store decompressed data. + /// The number of bytes written into the destination buffer. + public static int UnDeflate(this ArraySegment input, byte[] destination) + => UnDeflate(input.Array, input.Offset, input.Count, destination); + + /// + /// Decompresses a portion of a Deflate-compressed byte array into the provided destination buffer. + /// + /// The byte array containing Deflate-compressed data. + /// The starting index for decompression. + /// The number of bytes to decompress. + /// The buffer to store decompressed data. + /// The number of bytes written into the destination buffer. + public static int UnDeflate(this byte[] input, int index, int count, byte[] destination) + { + using var inputStream = new MemoryStream(input, index, count); + using var outputStream = new MemoryStream(destination); + AsyncContext.Run(() => inputStream.UncompressAsync(outputStream, true)); + return (int)outputStream.Position; + } + + /// + /// Compresses the specified byte array using the Deflate algorithm. + /// + /// The byte array to compress. + /// A compressed byte array. + public static byte[] DeflateTo(this byte[] input) + => input.Compress(); + + /// + /// Compresses a segment of a byte array using the Deflate algorithm. + /// + /// The byte array segment to compress. + /// A compressed byte array. + public static byte[] DeflateFrom(this ArraySegment v) + => v.Array.DeflateFrom(v.Offset, v.Count); + + /// + /// Compresses a portion of a byte array using the Deflate algorithm. + /// + /// The byte array to compress. + /// The starting index from which to begin compression. + /// The number of bytes to compress. + /// The buffer size to use during compression. + /// A compressed byte array. + public static byte[] DeflateFrom(this byte[] input, int? index = default, int? count = default, int bufferSize = DefaultBufferSize) + => input.Uncompress(index, count, bufferSize); + + /// + /// Compresses the specified byte array using the 7Zip (LZMA) algorithm. + /// + /// The byte array to compress. + /// A compressed byte array. + public static byte[] Do7Zip(this byte[] input) + => input.Compress(); + + /// + /// Decompresses a 7Zip (LZMA) compressed byte array. + /// + /// The byte array containing 7Zip-compressed data. + /// A decompressed byte array. + public static byte[] Un7Zip(this byte[] input) + => input.Uncompress(); + + /// + /// Compresses a portion of a byte array using the specified compression stream. + /// + /// The type of compression stream to use. + /// The byte array to compress. + /// The starting index for compression. + /// The number of bytes to compress. + /// The compression level to use. + /// The buffer size to use during compression. + /// A compressed byte array. + public static byte[] Compress(this byte[] input, int? index = default, int? count = default, CompressionLevel level = CompressionLevel.Optimal, int bufferSize = DefaultBufferSize) + where TCompressStream : Stream + => AsyncContext.Run(() => input.CompressAsync(index, count, level, bufferSize)); + + /// + /// Decompresses a portion of a byte array using the specified compression stream. + /// + /// The type of compression stream to use. + /// The byte array containing compressed data. + /// The starting index for decompression. + /// The number of bytes to decompress. + /// The buffer size to use during decompression. + /// A decompressed byte array. + public static byte[] Uncompress(this byte[] input, int? index = default, int? count = default, int bufferSize = DefaultBufferSize) + where TCompressStream : Stream + => AsyncContext.Run(() => input.UncompressAsync(index, count, bufferSize)); + + /// + /// Asynchronously compresses a segment of a byte array using the specified compression stream. + /// + /// The type of compression stream to use. + /// The segment of the byte array to compress. + /// The compression level to use. + /// The buffer size to use during compression. + /// A task representing the asynchronous operation, with a compressed byte array as the result. + public static Task CompressAsync(this ArraySegment v, CompressionLevel level = CompressionLevel.Optimal, int bufferSize = DefaultBufferSize) + where TCompressStream : Stream + => v.Array.CompressAsync(v.Offset, v.Count, level, bufferSize); + + /// + /// Asynchronously compresses a portion of a byte array using the specified compression stream. + /// + /// The type of compression stream to use. + /// The byte array to compress. + /// The starting index for compression. + /// The number of bytes to compress. + /// The compression level to use. + /// The buffer size to use during compression. + /// A token to monitor for cancellation requests. + /// A task representing the asynchronous operation, with a compressed byte array as the result. + public static async Task CompressAsync(this byte[] input, int? index = default, int? count = default, CompressionLevel level = CompressionLevel.Optimal, int bufferSize = DefaultBufferSize, CancellationToken cancellationToken = default) + where TCompressStream : Stream + { + if (input is null) + throw new ArgumentNullException(nameof(input)); + + using var inputStream = new MemoryStream(input, index ?? 0, count ?? input.Length); + using var outputStream = new MemoryStream(); + await inputStream.CompressAsync(outputStream, level, true, bufferSize, cancellationToken); + return outputStream.To(); + } + + /// + /// Asynchronously decompresses a portion of a byte array using the specified compression stream. + /// + /// The type of compression stream to use. + /// The byte array containing compressed data. + /// The starting index for decompression. + /// The number of bytes to decompress. + /// The buffer size to use during decompression. + /// A token to monitor for cancellation requests. + /// A task representing the asynchronous operation, with a decompressed byte array as the result. + public static async Task UncompressAsync(this byte[] input, int? index = default, int? count = default, int bufferSize = DefaultBufferSize, CancellationToken cancellationToken = default) + where TCompressStream : Stream + { + if (input is null) + throw new ArgumentNullException(nameof(input)); + + using var inputStream = new MemoryStream(input, index ?? 0, count ?? input.Length); + using var outputStream = new MemoryStream(); + await inputStream.UncompressAsync(outputStream, true, bufferSize, cancellationToken); + return outputStream.To(); + } + + /// + /// Asynchronously compresses data from the input stream and writes the compressed data to the output stream using the specified compression stream. + /// + /// The type of compression stream to use. + /// The input stream containing data to compress. + /// The output stream to write compressed data to. + /// The compression level to use. + /// Whether to leave the output stream open after compression. + /// The buffer size to use during compression. + /// A token to monitor for cancellation requests. + /// A task representing the asynchronous compression operation. + public static async Task CompressAsync(this Stream input, Stream output, CompressionLevel level = CompressionLevel.Optimal, bool leaveOpen = true, int bufferSize = DefaultBufferSize, CancellationToken cancellationToken = default) + where TCompressStream : Stream + { + if (input is null) + throw new ArgumentNullException(nameof(input)); + + using var compress = (TCompressStream)Activator.CreateInstance(typeof(TCompressStream), output, level, leaveOpen); + await input.CopyToAsync(compress, bufferSize, cancellationToken); + } + + /// + /// Asynchronously decompresses data from the input stream and writes the decompressed data to the output stream using the specified compression stream. + /// + /// The type of compression stream to use. + /// The input stream containing compressed data. + /// The output stream to write decompressed data to. + /// Whether to leave the input stream open after decompression. + /// The buffer size to use during decompression. + /// A token to monitor for cancellation requests. + /// A task representing the asynchronous decompression operation. + public static async Task UncompressAsync(this Stream input, Stream output, bool leaveOpen = true, int bufferSize = DefaultBufferSize, CancellationToken cancellationToken = default) + where TCompressStream : Stream + { + if (input is null) + throw new ArgumentNullException(nameof(input)); + + using var compress = (TCompressStream)Activator.CreateInstance(typeof(TCompressStream), input, CompressionMode.Decompress, leaveOpen); + await compress.CopyToAsync(output, bufferSize, cancellationToken); } } \ No newline at end of file diff --git a/Interop.Windows/Dde/DdeSettings.cs b/Interop.Windows/Dde/DdeSettings.cs index 84cd732b..aa41752f 100644 --- a/Interop.Windows/Dde/DdeSettings.cs +++ b/Interop.Windows/Dde/DdeSettings.cs @@ -1,126 +1,125 @@ -namespace Ecng.Interop.Dde +namespace Ecng.Interop.Dde; + +using System; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; + +using Ecng.Common; +using Ecng.Serialization; + +/// +/// Represents the settings for DDE (Dynamic Data Exchange) communication. +/// +[DisplayName("DDE settings")] +public class DdeSettings : Cloneable, IPersistable { - using System; - using System.ComponentModel; - using System.ComponentModel.DataAnnotations; + /// + /// Initializes a new instance of the class with default values. + /// + public DdeSettings() + { + Server = "EXCEL"; + Topic = "[Book1.xlsx]Sheet1"; + } + + /// + /// Gets or sets the DDE server name. + /// + [Display(Name = "Server", Description = "DDE server name.", Order = 0)] + public string Server { get; set; } + + /// + /// Gets or sets the DDE topic name (for example, "[Book1.xlsx]Sheet1"). + /// + [Display(Name = "Topic", Description = "Topic name (like [Book1.xlsx].Sheet1).", Order = 1)] + public string Topic { get; set; } - using Ecng.Common; - using Ecng.Serialization; + private int _columnOffset; /// - /// Represents the settings for DDE (Dynamic Data Exchange) communication. + /// Gets or sets the column offset from the left top corner. /// - [DisplayName("DDE settings")] - public class DdeSettings : Cloneable, IPersistable + /// Thrown when a negative value is assigned. + [Display(Name = "Column offset", Description = "Column offset from left top corner.", Order = 2)] + public int ColumnOffset { - /// - /// Initializes a new instance of the class with default values. - /// - public DdeSettings() + get { return _columnOffset; } + set { - Server = "EXCEL"; - Topic = "[Book1.xlsx]Sheet1"; - } + if (value < 0) + throw new ArgumentOutOfRangeException(); - /// - /// Gets or sets the DDE server name. - /// - [Display(Name = "Server", Description = "DDE server name.", Order = 0)] - public string Server { get; set; } - - /// - /// Gets or sets the DDE topic name (for example, "[Book1.xlsx]Sheet1"). - /// - [Display(Name = "Topic", Description = "Topic name (like [Book1.xlsx].Sheet1).", Order = 1)] - public string Topic { get; set; } - - private int _columnOffset; - - /// - /// Gets or sets the column offset from the left top corner. - /// - /// Thrown when a negative value is assigned. - [Display(Name = "Column offset", Description = "Column offset from left top corner.", Order = 2)] - public int ColumnOffset - { - get { return _columnOffset; } - set - { - if (value < 0) - throw new ArgumentOutOfRangeException(); - - _columnOffset = value; - } + _columnOffset = value; } + } - private int _rowOffset; + private int _rowOffset; - /// - /// Gets or sets the row offset from the left top corner. - /// - /// Thrown when a negative value is assigned. - [Display(Name = "Row offset", Description = "Row offset from left top corner.", Order = 2)] - public int RowOffset + /// + /// Gets or sets the row offset from the left top corner. + /// + /// Thrown when a negative value is assigned. + [Display(Name = "Row offset", Description = "Row offset from left top corner.", Order = 2)] + public int RowOffset + { + get { return _rowOffset; } + set { - get { return _rowOffset; } - set - { - if (value < 0) - throw new ArgumentOutOfRangeException(); - - _rowOffset = value; - } - } + if (value < 0) + throw new ArgumentOutOfRangeException(); - /// - /// Gets or sets a value indicating whether header names should be displayed. - /// - [Display(Name = "Headers", Description = "Show headers name.", Order = 2)] - public bool ShowHeaders { get; set; } - - /// - /// Applies the settings from the specified instance. - /// - /// The instance containing the new settings. - public void Apply(DdeSettings clone) - { - PersistableHelper.Apply(this, clone); + _rowOffset = value; } + } - /// - /// Creates a deep copy of the current instance. - /// - /// A new instance that is a deep copy of this instance. - public override DdeSettings Clone() - { - return PersistableHelper.Clone(this); - } + /// + /// Gets or sets a value indicating whether header names should be displayed. + /// + [Display(Name = "Headers", Description = "Show headers name.", Order = 2)] + public bool ShowHeaders { get; set; } - /// - /// Loads the settings from the provided . - /// - /// The storage from which to load the settings. - public void Load(SettingsStorage storage) - { - Server = storage.GetValue(nameof(Server)); - Topic = storage.GetValue(nameof(Topic)); - ColumnOffset = storage.GetValue(nameof(ColumnOffset)); - RowOffset = storage.GetValue(nameof(RowOffset)); - ShowHeaders = storage.GetValue(nameof(ShowHeaders)); - } + /// + /// Applies the settings from the specified instance. + /// + /// The instance containing the new settings. + public void Apply(DdeSettings clone) + { + PersistableHelper.Apply(this, clone); + } - /// - /// Saves the current settings to the provided . - /// - /// The storage to which the settings will be saved. - public void Save(SettingsStorage storage) - { - storage - .Set(nameof(Server), Server) - .Set(nameof(Topic), Topic) - .Set(nameof(ColumnOffset), ColumnOffset) - .Set(nameof(RowOffset), RowOffset) - .Set(nameof(ShowHeaders), ShowHeaders); - } + /// + /// Creates a deep copy of the current instance. + /// + /// A new instance that is a deep copy of this instance. + public override DdeSettings Clone() + { + return PersistableHelper.Clone(this); + } + + /// + /// Loads the settings from the provided . + /// + /// The storage from which to load the settings. + public void Load(SettingsStorage storage) + { + Server = storage.GetValue(nameof(Server)); + Topic = storage.GetValue(nameof(Topic)); + ColumnOffset = storage.GetValue(nameof(ColumnOffset)); + RowOffset = storage.GetValue(nameof(RowOffset)); + ShowHeaders = storage.GetValue(nameof(ShowHeaders)); + } + + /// + /// Saves the current settings to the provided . + /// + /// The storage to which the settings will be saved. + public void Save(SettingsStorage storage) + { + storage + .Set(nameof(Server), Server) + .Set(nameof(Topic), Topic) + .Set(nameof(ColumnOffset), ColumnOffset) + .Set(nameof(RowOffset), RowOffset) + .Set(nameof(ShowHeaders), ShowHeaders); } } \ No newline at end of file diff --git a/Interop.Windows/Dde/XlsDdeClient.cs b/Interop.Windows/Dde/XlsDdeClient.cs index be8efb2a..adc98591 100644 --- a/Interop.Windows/Dde/XlsDdeClient.cs +++ b/Interop.Windows/Dde/XlsDdeClient.cs @@ -1,84 +1,83 @@ -namespace Ecng.Interop.Dde +namespace Ecng.Interop.Dde; + +using System; +using System.Collections.Generic; + +using Ecng.Common; + +using NDde.Client; + +/// +/// A client for interacting with Excel via Dynamic Data Exchange (DDE), providing methods to start, stop, and send data. +/// +public class XlsDdeClient(DdeSettings settings) : Disposable { - using System; - using System.Collections.Generic; + private DdeClient _client; - using Ecng.Common; + /// + /// Gets a value indicating whether the DDE client is currently started and connected. + /// + public bool IsStarted => _client != null; - using NDde.Client; + /// + /// Gets the settings used to configure the DDE client. + /// + /// Thrown when the settings provided during construction are null. + public DdeSettings Settings { get; } = settings ?? throw new ArgumentNullException(nameof(settings)); /// - /// A client for interacting with Excel via Dynamic Data Exchange (DDE), providing methods to start, stop, and send data. + /// Starts the DDE client and establishes a connection to the specified server and topic. /// - public class XlsDdeClient(DdeSettings settings) : Disposable + public void Start() { - private DdeClient _client; - - /// - /// Gets a value indicating whether the DDE client is currently started and connected. - /// - public bool IsStarted => _client != null; - - /// - /// Gets the settings used to configure the DDE client. - /// - /// Thrown when the settings provided during construction are null. - public DdeSettings Settings { get; } = settings ?? throw new ArgumentNullException(nameof(settings)); - - /// - /// Starts the DDE client and establishes a connection to the specified server and topic. - /// - public void Start() - { - _client = new DdeClient(Settings.Server, Settings.Topic); - _client.Connect(); - } - - /// - /// Stops the DDE client and disconnects from the server if currently connected. - /// - public void Stop() - { - if (_client.IsConnected) - _client.Disconnect(); - - _client = null; - } - - /// - /// Sends a block of data to Excel via DDE using the specified row and column offsets. - /// - /// A list of rows, where each row is a list of cell values to send. - /// Thrown when is null. - /// Thrown when is empty. - public void Poke(IList> rows) - { - if (rows is null) - throw new ArgumentNullException(nameof(rows)); - - if (rows.Count == 0) - throw new ArgumentOutOfRangeException(nameof(rows)); - - if (!Settings.ShowHeaders) - rows.RemoveAt(0); - - var rowStart = 1 + Settings.RowOffset; - var columnStart = 1 + Settings.ColumnOffset; - var colCount = rows.Count == 0 ? 0 : rows[0].Count; - - _client.Poke($"R{rowStart}C{columnStart}:R{rowStart + rows.Count}C{columnStart + colCount}", - XlsDdeSerializer.Serialize(rows), 0x0090 | 0x4000, (int)TimeSpan.FromSeconds(10).TotalMilliseconds); - } - - /// - /// Releases managed resources by stopping the DDE client if it is started. - /// - protected override void DisposeManaged() - { - if (IsStarted) - Stop(); - - base.DisposeManaged(); - } + _client = new DdeClient(Settings.Server, Settings.Topic); + _client.Connect(); + } + + /// + /// Stops the DDE client and disconnects from the server if currently connected. + /// + public void Stop() + { + if (_client.IsConnected) + _client.Disconnect(); + + _client = null; + } + + /// + /// Sends a block of data to Excel via DDE using the specified row and column offsets. + /// + /// A list of rows, where each row is a list of cell values to send. + /// Thrown when is null. + /// Thrown when is empty. + public void Poke(IList> rows) + { + if (rows is null) + throw new ArgumentNullException(nameof(rows)); + + if (rows.Count == 0) + throw new ArgumentOutOfRangeException(nameof(rows)); + + if (!Settings.ShowHeaders) + rows.RemoveAt(0); + + var rowStart = 1 + Settings.RowOffset; + var columnStart = 1 + Settings.ColumnOffset; + var colCount = rows.Count == 0 ? 0 : rows[0].Count; + + _client.Poke($"R{rowStart}C{columnStart}:R{rowStart + rows.Count}C{columnStart + colCount}", + XlsDdeSerializer.Serialize(rows), 0x0090 | 0x4000, (int)TimeSpan.FromSeconds(10).TotalMilliseconds); + } + + /// + /// Releases managed resources by stopping the DDE client if it is started. + /// + protected override void DisposeManaged() + { + if (IsStarted) + Stop(); + + base.DisposeManaged(); } } \ No newline at end of file diff --git a/Interop.Windows/Dde/XlsDdeSerializer.cs b/Interop.Windows/Dde/XlsDdeSerializer.cs index 26d4032a..a1fb16d0 100644 --- a/Interop.Windows/Dde/XlsDdeSerializer.cs +++ b/Interop.Windows/Dde/XlsDdeSerializer.cs @@ -1,182 +1,181 @@ -namespace Ecng.Interop.Dde -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Text; +namespace Ecng.Interop.Dde; - using Ecng.Common; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; - /// - /// Provides methods for serializing and deserializing data to and from a format compatible with Excel DDE. - /// - static class XlsDdeSerializer +using Ecng.Common; + +/// +/// Provides methods for serializing and deserializing data to and from a format compatible with Excel DDE. +/// +static class XlsDdeSerializer +{ + private enum DataTypes : short { - private enum DataTypes : short - { - Table = 0x0010, - Float = 0x0001, - String = 0x0002, - Bool = 0x0003, - Error = 0x0004, - Blank = 0x0005, - Int = 0x0006, - Skip = 0x0007, - } + Table = 0x0010, + Float = 0x0001, + String = 0x0002, + Bool = 0x0003, + Error = 0x0004, + Blank = 0x0005, + Int = 0x0006, + Skip = 0x0007, + } - private static Type GetCompType(DataTypes type) + private static Type GetCompType(DataTypes type) + { + return type switch { - return type switch - { - DataTypes.Float => typeof(double), - DataTypes.String => typeof(string), - DataTypes.Bool => typeof(bool), - DataTypes.Int => typeof(int), - DataTypes.Skip => null, - _ => throw new ArgumentOutOfRangeException(nameof(type)), - }; - } + DataTypes.Float => typeof(double), + DataTypes.String => typeof(string), + DataTypes.Bool => typeof(bool), + DataTypes.Int => typeof(int), + DataTypes.Skip => null, + _ => throw new ArgumentOutOfRangeException(nameof(type)), + }; + } - private static DataTypes GetXlsType(object cell) - { - if (cell is null) - return DataTypes.Blank; + private static DataTypes GetXlsType(object cell) + { + if (cell is null) + return DataTypes.Blank; - if (cell is float || cell is double || cell is decimal) - return DataTypes.Float; + if (cell is float || cell is double || cell is decimal) + return DataTypes.Float; - if (cell is byte || cell is sbyte || cell is short || cell is int || cell is long || cell is ushort || cell is uint || cell is ulong) - return DataTypes.Int; + if (cell is byte || cell is sbyte || cell is short || cell is int || cell is long || cell is ushort || cell is uint || cell is ulong) + return DataTypes.Int; - if (cell is string) - return DataTypes.String; + if (cell is string) + return DataTypes.String; - if (cell is bool) - return DataTypes.Bool; + if (cell is bool) + return DataTypes.Bool; - throw new ArgumentException($"Unknown cell value type '{cell.GetType()}'.", nameof(cell)); - } + throw new ArgumentException($"Unknown cell value type '{cell.GetType()}'.", nameof(cell)); + } - /// - /// Serializes a table of data into a byte array compatible with Excel DDE. - /// - /// A list of rows, where each row is a list of cell values to serialize. - /// A byte array representing the serialized data. - /// Thrown when an unsupported cell type is encountered. - public static byte[] Serialize(IList> rows) - { - var stream = new MemoryStream(); + /// + /// Serializes a table of data into a byte array compatible with Excel DDE. + /// + /// A list of rows, where each row is a list of cell values to serialize. + /// A byte array representing the serialized data. + /// Thrown when an unsupported cell type is encountered. + public static byte[] Serialize(IList> rows) + { + var stream = new MemoryStream(); - stream.WriteEx(DataTypes.Table); - stream.WriteEx((short)4); - stream.WriteEx((short)rows.Count); - stream.WriteEx((short)(rows.Count == 0 ? 0 : rows[0].Count)); + stream.WriteEx(DataTypes.Table); + stream.WriteEx((short)4); + stream.WriteEx((short)rows.Count); + stream.WriteEx((short)(rows.Count == 0 ? 0 : rows[0].Count)); - foreach (var row in rows) + foreach (var row in rows) + { + foreach (var cell in row) { - foreach (var cell in row) - { - var cellDt = GetXlsType(cell); + var cellDt = GetXlsType(cell); - switch (cellDt) - { - case DataTypes.Float: - stream.WriteEx((short)8); - stream.WriteEx(cell); - break; - case DataTypes.String: - var str = (string)cell; - stream.WriteEx((byte)str.Length); - stream.WriteRaw(str.Default()); - break; - case DataTypes.Bool: - stream.WriteEx((short)2); - stream.WriteEx(cell); - break; - case DataTypes.Blank: - stream.WriteEx((short)2); - stream.WriteEx(new byte[2]); - break; - case DataTypes.Int: - stream.WriteEx((short)4); - stream.WriteEx(cell); - break; - default: - throw new ArgumentOutOfRangeException(cellDt.To()); - } + switch (cellDt) + { + case DataTypes.Float: + stream.WriteEx((short)8); + stream.WriteEx(cell); + break; + case DataTypes.String: + var str = (string)cell; + stream.WriteEx((byte)str.Length); + stream.WriteRaw(str.Default()); + break; + case DataTypes.Bool: + stream.WriteEx((short)2); + stream.WriteEx(cell); + break; + case DataTypes.Blank: + stream.WriteEx((short)2); + stream.WriteEx(new byte[2]); + break; + case DataTypes.Int: + stream.WriteEx((short)4); + stream.WriteEx(cell); + break; + default: + throw new ArgumentOutOfRangeException(cellDt.To()); } } - - return stream.ToArray(); } - /// - /// Deserializes a byte array from Excel DDE format into a table of data. - /// - /// The byte array containing the serialized DDE data. - /// A list of rows, where each row is a list of cell values. - /// Thrown when is null. - public static IList> Deserialize(byte[] data) - { - if (data is null) - throw new ArgumentNullException(nameof(data)); + return stream.ToArray(); + } - var stream = data.To(); + /// + /// Deserializes a byte array from Excel DDE format into a table of data. + /// + /// The byte array containing the serialized DDE data. + /// A list of rows, where each row is a list of cell values. + /// Thrown when is null. + public static IList> Deserialize(byte[] data) + { + if (data is null) + throw new ArgumentNullException(nameof(data)); - /*var dt = */stream.Read(); - /*var size = */stream.Read(); - var rowCount = stream.Read(); - var columnCount = stream.Read(); + var stream = data.To(); - var rows = new List>(); + /*var dt = */stream.Read(); + /*var size = */stream.Read(); + var rowCount = stream.Read(); + var columnCount = stream.Read(); - for (var row = 0; row < rowCount; row++) + var rows = new List>(); + + for (var row = 0; row < rowCount; row++) + { + var cells = new List(); + + do { - var cells = new List(); + var cellDt = stream.Read(); + var cellSize = stream.Read(); - do + if (cellDt != DataTypes.Skip) { - var cellDt = stream.Read(); - var cellSize = stream.Read(); - - if (cellDt != DataTypes.Skip) - { - var type = GetCompType(cellDt); - var typeSize = type == typeof(string) ? cellSize : type.SizeOf(); + var type = GetCompType(cellDt); + var typeSize = type == typeof(string) ? cellSize : type.SizeOf(); - var cellColumnCount = cellSize / typeSize; + var cellColumnCount = cellSize / typeSize; - for (var column = 0; column < cellColumnCount; column++) + for (var column = 0; column < cellColumnCount; column++) + { + if (type == typeof(string)) { - if (type == typeof(string)) + var pos = 0; + var buffer = stream.ReadBuffer(typeSize); + while (pos < buffer.Length) { - var pos = 0; - var buffer = stream.ReadBuffer(typeSize); - while (pos < buffer.Length) - { - var len = buffer[pos]; - var str = new byte[len]; - Buffer.BlockCopy(buffer, pos + 1, str, 0, len); - cells.Add(Encoding.Default.GetString(str)); - pos += len + 1; - } + var len = buffer[pos]; + var str = new byte[len]; + Buffer.BlockCopy(buffer, pos + 1, str, 0, len); + cells.Add(Encoding.Default.GetString(str)); + pos += len + 1; } - else - cells.Add(stream.Read(type)); } - } - else - { - stream.ReadBuffer(cellSize); - cells.AddRange(new object[cellSize]); + else + cells.Add(stream.Read(type)); } } - while (cells.Count < columnCount); - - rows.Add(cells); + else + { + stream.ReadBuffer(cellSize); + cells.AddRange(new object[cellSize]); + } } + while (cells.Count < columnCount); - return rows; + rows.Add(cells); } + + return rows; } } \ No newline at end of file diff --git a/Interop.Windows/Dde/XlsDdeServer.cs b/Interop.Windows/Dde/XlsDdeServer.cs index ec7ea855..656b75fc 100644 --- a/Interop.Windows/Dde/XlsDdeServer.cs +++ b/Interop.Windows/Dde/XlsDdeServer.cs @@ -1,187 +1,186 @@ -namespace Ecng.Interop.Dde -{ - using System; - using System.Collections.Generic; - using System.Threading; +namespace Ecng.Interop.Dde; + +using System; +using System.Collections.Generic; +using System.Threading; - using Ecng.Collections; - using Ecng.Common; +using Ecng.Collections; +using Ecng.Common; - using NDde.Server; +using NDde.Server; +/// +/// Provides a DDE server for Excel that handles poke requests and advises clients of updated data. +/// +[CLSCompliant(false)] +public class XlsDdeServer(string service, Action>> poke, Action error) : DdeServer(service) +{ /// - /// Provides a DDE server for Excel that handles poke requests and advises clients of updated data. + /// Private helper class that dispatches events on dedicated threads. /// - [CLSCompliant(false)] - public class XlsDdeServer(string service, Action>> poke, Action error) : DdeServer(service) + private class EventDispatcher(Action errorHandler) : Disposable { + private readonly Action _errorHandler = errorHandler ?? throw new ArgumentNullException(nameof(errorHandler)); + private readonly SynchronizedDictionary> _events = []; + /// - /// Private helper class that dispatches events on dedicated threads. + /// Adds an event to be executed. /// - private class EventDispatcher(Action errorHandler) : Disposable + /// The event action to add. + public void Add(Action evt) { - private readonly Action _errorHandler = errorHandler ?? throw new ArgumentNullException(nameof(errorHandler)); - private readonly SynchronizedDictionary> _events = []; - - /// - /// Adds an event to be executed. - /// - /// The event action to add. - public void Add(Action evt) - { - Add(evt, string.Empty); - } + Add(evt, string.Empty); + } + + /// + /// Adds an event to be executed with a synchronization token. + /// + /// The event action to add. + /// The synchronization token to group events. + public virtual void Add(Action evt, string syncToken) + { + if (evt is null) + throw new ArgumentNullException(nameof(evt)); + + var queue = _events.SafeAdd(syncToken, CreateNewThreadQueuePair); - /// - /// Adds an event to be executed with a synchronization token. - /// - /// The event action to add. - /// The synchronization token to group events. - public virtual void Add(Action evt, string syncToken) + queue.Enqueue(() => { - if (evt is null) - throw new ArgumentNullException(nameof(evt)); + try + { + evt(); + } + catch (Exception ex) + { + _errorHandler(ex); + } + }); + } - var queue = _events.SafeAdd(syncToken, CreateNewThreadQueuePair); + private static BlockingQueue CreateNewThreadQueuePair(string syncToken) + { + var queue = new BlockingQueue(); - queue.Enqueue(() => + ThreadingHelper + .Thread(() => { - try + while (!queue.IsClosed) { + if (!queue.TryDequeue(out var evt)) + break; + evt(); } - catch (Exception ex) - { - _errorHandler(ex); - } - }); - } - - private static BlockingQueue CreateNewThreadQueuePair(string syncToken) - { - var queue = new BlockingQueue(); - - ThreadingHelper - .Thread(() => - { - while (!queue.IsClosed) - { - if (!queue.TryDequeue(out var evt)) - break; - - evt(); - } - }) - .Name("EventDispatcher thread #" + syncToken) - .Start(); - - return queue; - } + }) + .Name("EventDispatcher thread #" + syncToken) + .Start(); - /// - /// Disposes the managed resources by closing all event queues. - /// - protected override void DisposeManaged() - { - _events.SyncDo(d => d.ForEach(p => p.Value.Close())); - base.DisposeManaged(); - } + return queue; } - private readonly SyncObject _registerWait = new(); - private Timer _adviseTimer; - private readonly EventDispatcher _dispather = new(error); - private readonly Action>> _poke = poke ?? throw new ArgumentNullException(nameof(poke)); - private readonly Action _error = error ?? throw new ArgumentNullException(nameof(error)); - /// - /// Starts the DDE server and initializes the timer to advise clients. + /// Disposes the managed resources by closing all event queues. /// - public void Start() + protected override void DisposeManaged() { - Exception error = null; + _events.SyncDo(d => d.ForEach(p => p.Value.Close())); + base.DisposeManaged(); + } + } - var regLock = new SyncObject(); + private readonly SyncObject _registerWait = new(); + private Timer _adviseTimer; + private readonly EventDispatcher _dispather = new(error); + private readonly Action>> _poke = poke ?? throw new ArgumentNullException(nameof(poke)); + private readonly Action _error = error ?? throw new ArgumentNullException(nameof(error)); - lock (regLock) - { - ThreadingHelper - .Thread(() => - { - try - { - Register(); - regLock.Pulse(); - - _registerWait.Wait(); - } - catch (Exception ex) - { - error = ex; - regLock.Pulse(); - } - }) - .Name("Dde thread") - .Launch(); - - Monitor.Wait(regLock); - } + /// + /// Starts the DDE server and initializes the timer to advise clients. + /// + public void Start() + { + Exception error = null; - if (error != null) - throw new InvalidOperationException(" DDE .", error); + var regLock = new SyncObject(); - // Create a timer that will be used to advise clients of new data. - _adviseTimer = ThreadingHelper.Timer(() => - { - try - { - // Advise all topic name and item name pairs. - Advise("*", "*"); - } - catch (Exception ex) + lock (regLock) + { + ThreadingHelper + .Thread(() => { - _error(ex); - } - }) - .Interval(TimeSpan.FromSeconds(1)); + try + { + Register(); + regLock.Pulse(); + + _registerWait.Wait(); + } + catch (Exception ex) + { + error = ex; + regLock.Pulse(); + } + }) + .Name("Dde thread") + .Launch(); + + Monitor.Wait(regLock); } - /// - /// Handles poke requests from DDE conversations. - /// - /// The DDE conversation instance. - /// The item name requested. - /// The data payload in byte array format. - /// The format of the data received. - /// A result indicating that the poke has been processed. - protected override PokeResult OnPoke(DdeConversation conversation, string item, byte[] data, int format) + if (error != null) + throw new InvalidOperationException(" DDE .", error); + + // Create a timer that will be used to advise clients of new data. + _adviseTimer = ThreadingHelper.Timer(() => { - _dispather.Add(() => + try { - var rows = XlsDdeSerializer.Deserialize(data); - _poke(conversation.Topic, rows); - }, conversation.Topic); - - return PokeResult.Processed; - } + // Advise all topic name and item name pairs. + Advise("*", "*"); + } + catch (Exception ex) + { + _error(ex); + } + }) + .Interval(TimeSpan.FromSeconds(1)); + } - /// - /// Releases the unmanaged resources and, optionally, the managed resources. - /// - /// True to release both managed and unmanaged resources; false to release only unmanaged resources. - protected override void Dispose(bool disposing) + /// + /// Handles poke requests from DDE conversations. + /// + /// The DDE conversation instance. + /// The item name requested. + /// The data payload in byte array format. + /// The format of the data received. + /// A result indicating that the poke has been processed. + protected override PokeResult OnPoke(DdeConversation conversation, string item, byte[] data, int format) + { + _dispather.Add(() => { - _dispather.Dispose(); + var rows = XlsDdeSerializer.Deserialize(data); + _poke(conversation.Topic, rows); + }, conversation.Topic); - if (disposing) - { - if (!_adviseTimer.IsNull()) - _adviseTimer.Dispose(); + return PokeResult.Processed; + } - _registerWait.Pulse(); - } + /// + /// Releases the unmanaged resources and, optionally, the managed resources. + /// + /// True to release both managed and unmanaged resources; false to release only unmanaged resources. + protected override void Dispose(bool disposing) + { + _dispather.Dispose(); - base.Dispose(disposing); + if (disposing) + { + if (!_adviseTimer.IsNull()) + _adviseTimer.Dispose(); + + _registerWait.Pulse(); } + + base.Dispose(disposing); } } \ No newline at end of file diff --git a/Interop.Windows/WinApi.cs b/Interop.Windows/WinApi.cs index 51de6c30..d0350988 100644 --- a/Interop.Windows/WinApi.cs +++ b/Interop.Windows/WinApi.cs @@ -1,54 +1,53 @@ -namespace Ecng.Interop +namespace Ecng.Interop; + +using System; +using System.Windows.Forms; + +using Microsoft.Win32; + +/// +/// Provides Windows API utility methods. +/// +[CLSCompliant(false)] +public static class WinApi { - using System; - using System.Windows.Forms; + /// + /// Retrieves the screen boundaries (left, top, width, height) for the screen that contains the specified window handle. + /// + /// The handle of the window. + /// Output parameter that returns the left coordinate of the screen. + /// Output parameter that returns the top coordinate of the screen. + /// Output parameter that returns the width of the screen. + /// Output parameter that returns the height of the screen. + public static void GetScreenParams(IntPtr hwnd, out int left, out int top, out int width, out int height) + { + var activeScreen = Screen.FromHandle(hwnd); + var bounds = activeScreen.Bounds; + + left = bounds.Left; + top = bounds.Top; + width = bounds.Width; + height = bounds.Height; + } - using Microsoft.Win32; + private const string _path = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Run"; + + private static RegistryKey BaseKey => Registry.CurrentUser; /// - /// Provides Windows API utility methods. + /// Updates the auto-run registry entry for a given application. /// - [CLSCompliant(false)] - public static class WinApi + /// The name of the application. + /// The full path to the application executable. + /// True to enable auto-run; false to disable it. + /// Thrown when the autorun registry key cannot be found. + public static void UpdateAutoRun(string appName, string path, bool enabled) { - /// - /// Retrieves the screen boundaries (left, top, width, height) for the screen that contains the specified window handle. - /// - /// The handle of the window. - /// Output parameter that returns the left coordinate of the screen. - /// Output parameter that returns the top coordinate of the screen. - /// Output parameter that returns the width of the screen. - /// Output parameter that returns the height of the screen. - public static void GetScreenParams(IntPtr hwnd, out int left, out int top, out int width, out int height) - { - var activeScreen = Screen.FromHandle(hwnd); - var bounds = activeScreen.Bounds; - - left = bounds.Left; - top = bounds.Top; - width = bounds.Width; - height = bounds.Height; - } - - private const string _path = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Run"; - - private static RegistryKey BaseKey => Registry.CurrentUser; - - /// - /// Updates the auto-run registry entry for a given application. - /// - /// The name of the application. - /// The full path to the application executable. - /// True to enable auto-run; false to disable it. - /// Thrown when the autorun registry key cannot be found. - public static void UpdateAutoRun(string appName, string path, bool enabled) - { - using var key = BaseKey.OpenSubKey(_path, true) ?? throw new InvalidOperationException($"autorun not found ({_path})"); - - if (enabled) - key.SetValue(appName, path); - else - key.DeleteValue(appName, false); - } + using var key = BaseKey.OpenSubKey(_path, true) ?? throw new InvalidOperationException($"autorun not found ({_path})"); + + if (enabled) + key.SetValue(appName, path); + else + key.DeleteValue(appName, false); } } \ No newline at end of file diff --git a/Interop.Windows/WindowsThreadingHelper.cs b/Interop.Windows/WindowsThreadingHelper.cs index a42600cf..075697d1 100644 --- a/Interop.Windows/WindowsThreadingHelper.cs +++ b/Interop.Windows/WindowsThreadingHelper.cs @@ -1,100 +1,99 @@ -namespace Ecng.Interop -{ - using System; - using System.Threading; +namespace Ecng.Interop; + +using System; +using System.Threading; - using Ecng.Common; +using Ecng.Common; +/// +/// Provides helper methods to run code on threads with specific apartment states. +/// +public static class WindowsThreadingHelper +{ /// - /// Provides helper methods to run code on threads with specific apartment states. + /// Sets the apartment state of the specified thread to single-threaded apartment (STA). /// - public static class WindowsThreadingHelper + /// The thread to set the apartment state for. + /// The same thread with the updated apartment state. + /// Thrown when the thread is null. + public static Thread STA(this Thread thread) { - /// - /// Sets the apartment state of the specified thread to single-threaded apartment (STA). - /// - /// The thread to set the apartment state for. - /// The same thread with the updated apartment state. - /// Thrown when the thread is null. - public static Thread STA(this Thread thread) - { - if (thread is null) - throw new ArgumentNullException(nameof(thread)); + if (thread is null) + throw new ArgumentNullException(nameof(thread)); - thread.SetApartmentState(ApartmentState.STA); - return thread; - } + thread.SetApartmentState(ApartmentState.STA); + return thread; + } - /// - /// Sets the apartment state of the specified thread to multi-threaded apartment (MTA). - /// - /// The thread to set the apartment state for. - /// The same thread with the updated apartment state. - /// Thrown when the thread is null. - public static Thread MTA(this Thread thread) - { - if (thread is null) - throw new ArgumentNullException(nameof(thread)); + /// + /// Sets the apartment state of the specified thread to multi-threaded apartment (MTA). + /// + /// The thread to set the apartment state for. + /// The same thread with the updated apartment state. + /// Thrown when the thread is null. + public static Thread MTA(this Thread thread) + { + if (thread is null) + throw new ArgumentNullException(nameof(thread)); - thread.SetApartmentState(ApartmentState.MTA); - return thread; - } + thread.SetApartmentState(ApartmentState.MTA); + return thread; + } - /// - /// Invokes the specified action on a new STA (single-threaded apartment) thread. - /// - /// The action to invoke. - /// Thrown when the action is null. - public static void InvokeAsSTA(this Action action) - { - if (action is null) - throw new ArgumentNullException(nameof(action)); + /// + /// Invokes the specified action on a new STA (single-threaded apartment) thread. + /// + /// The action to invoke. + /// Thrown when the action is null. + public static void InvokeAsSTA(this Action action) + { + if (action is null) + throw new ArgumentNullException(nameof(action)); - InvokeAsSTA(() => - { - action(); - return null; - }); - } + InvokeAsSTA(() => + { + action(); + return null; + }); + } - // http://stackoverflow.com/questions/518701/clipboard-gettext-returns-null-empty-string + // http://stackoverflow.com/questions/518701/clipboard-gettext-returns-null-empty-string - /// - /// Invokes the specified function on a new STA (single-threaded apartment) thread and returns a result. - /// - /// The type of the return value. - /// The function to invoke. - /// The result returned by the function. - /// Thrown when the function is null. - /// Throws any exception that occurs during the function execution. - public static T InvokeAsSTA(this Func func) - { - if (func is null) - throw new ArgumentNullException(nameof(func)); + /// + /// Invokes the specified function on a new STA (single-threaded apartment) thread and returns a result. + /// + /// The type of the return value. + /// The function to invoke. + /// The result returned by the function. + /// Thrown when the function is null. + /// Throws any exception that occurs during the function execution. + public static T InvokeAsSTA(this Func func) + { + if (func is null) + throw new ArgumentNullException(nameof(func)); - T retVal = default; - Exception threadEx = null; + T retVal = default; + Exception threadEx = null; - var staThread = ThreadingHelper.Thread(() => + var staThread = ThreadingHelper.Thread(() => + { + try + { + retVal = func(); + } + catch (Exception ex) { - try - { - retVal = func(); - } - catch (Exception ex) - { - threadEx = ex; - } - }) - .STA() - .Launch(); + threadEx = ex; + } + }) + .STA() + .Launch(); - staThread.Join(); + staThread.Join(); - if (threadEx != null) - throw threadEx; + if (threadEx != null) + throw threadEx; - return retVal; - } + return retVal; } } \ No newline at end of file diff --git a/Interop/BlittableDecimal.cs b/Interop/BlittableDecimal.cs index 745aa0ea..0c9072c0 100644 --- a/Interop/BlittableDecimal.cs +++ b/Interop/BlittableDecimal.cs @@ -1,61 +1,60 @@ -namespace Ecng.Interop -{ - using System.Runtime.InteropServices; +namespace Ecng.Interop; + +using System.Runtime.InteropServices; - using Ecng.Common; +using Ecng.Common; + +/// +/// Represents a blittable version of a decimal value. +/// +[StructLayout(LayoutKind.Sequential)] +public struct BlittableDecimal +{ + private int _bit0; + private int _bit1; + private int _bit2; + private int _bit3; /// - /// Represents a blittable version of a decimal value. + /// Gets or sets the decimal value. /// - [StructLayout(LayoutKind.Sequential)] - public struct BlittableDecimal + public decimal Value { - private int _bit0; - private int _bit1; - private int _bit2; - private int _bit3; - - /// - /// Gets or sets the decimal value. - /// - public decimal Value + get => new[] { _bit0, _bit1, _bit2, _bit3 }.To(); + set { - get => new[] { _bit0, _bit1, _bit2, _bit3 }.To(); - set - { - var bits = value.To(); - - _bit0 = bits[0]; - _bit1 = bits[1]; - _bit2 = bits[2]; - _bit3 = bits[3]; - } - } + var bits = value.To(); - /// - /// Converts a decimal value to a . - /// - /// The decimal value to convert. - /// A representing the decimal value. - public static explicit operator BlittableDecimal(decimal value) - { - return new BlittableDecimal { Value = value }; + _bit0 = bits[0]; + _bit1 = bits[1]; + _bit2 = bits[2]; + _bit3 = bits[3]; } + } - /// - /// Converts a to a decimal value. - /// - /// The to convert. - /// A decimal value. - public static implicit operator decimal(BlittableDecimal value) - { - return value.Value; - } + /// + /// Converts a decimal value to a . + /// + /// The decimal value to convert. + /// A representing the decimal value. + public static explicit operator BlittableDecimal(decimal value) + { + return new BlittableDecimal { Value = value }; + } - /// - /// Returns a string that represents the decimal value. - /// - /// A string representation of the decimal value. - public override string ToString() => Value.ToString(); + /// + /// Converts a to a decimal value. + /// + /// The to convert. + /// A decimal value. + public static implicit operator decimal(BlittableDecimal value) + { + return value.Value; } + + /// + /// Returns a string that represents the decimal value. + /// + /// A string representation of the decimal value. + public override string ToString() => Value.ToString(); } \ No newline at end of file diff --git a/Interop/DllLibrary.cs b/Interop/DllLibrary.cs index 57cfdc93..e78273d9 100644 --- a/Interop/DllLibrary.cs +++ b/Interop/DllLibrary.cs @@ -1,62 +1,61 @@ -namespace Ecng.Interop +namespace Ecng.Interop; + +using System; +using System.Diagnostics; + +using Ecng.Common; + +/// +/// Represents a base class that manages a DLL library and provides access to its exported functions. +/// +/// The file path to the DLL. +public abstract class DllLibrary(string dllPath) : Disposable { - using System; - using System.Diagnostics; + /// + /// Gets the file path to the DLL. + /// + public string DllPath { get; private set; } = dllPath.ThrowIfEmpty(nameof(dllPath)); + + private Version _dllVersion; + + /// + /// Gets the version of the DLL by retrieving product information. + /// + public Version DllVersion => _dllVersion ??= + FileVersionInfo.GetVersionInfo(DllPath).ProductVersion? + .Replace(',', '.') + ?.RemoveSpaces() + ?.To(); - using Ecng.Common; + /// + /// Gets the pointer to the loaded DLL. + /// + protected IntPtr Handler { get; } = Marshaler.LoadLibrary(dllPath); + + /// + /// Retrieves a function pointer from the DLL and casts it to the specified delegate type. + /// + /// The type of the delegate. + /// The name of the procedure to retrieve. + /// A delegate of type . + protected T GetHandler(string procName) => Handler.GetHandler(procName); + + /// + /// Attempts to retrieve a function pointer from the DLL as the specified delegate type. Returns null if not found. + /// + /// The type of the delegate. + /// The name of the procedure to retrieve. + /// A delegate of type , or null if the procedure is not found. + protected T TryGetHandler(string procName) + where T : Delegate + => Handler.TryGetHandler(procName); /// - /// Represents a base class that manages a DLL library and provides access to its exported functions. + /// Disposes native resources associated with the DLL. /// - /// The file path to the DLL. - public abstract class DllLibrary(string dllPath) : Disposable + protected override void DisposeNative() { - /// - /// Gets the file path to the DLL. - /// - public string DllPath { get; private set; } = dllPath.ThrowIfEmpty(nameof(dllPath)); - - private Version _dllVersion; - - /// - /// Gets the version of the DLL by retrieving product information. - /// - public Version DllVersion => _dllVersion ??= - FileVersionInfo.GetVersionInfo(DllPath).ProductVersion? - .Replace(',', '.') - ?.RemoveSpaces() - ?.To(); - - /// - /// Gets the pointer to the loaded DLL. - /// - protected IntPtr Handler { get; } = Marshaler.LoadLibrary(dllPath); - - /// - /// Retrieves a function pointer from the DLL and casts it to the specified delegate type. - /// - /// The type of the delegate. - /// The name of the procedure to retrieve. - /// A delegate of type . - protected T GetHandler(string procName) => Handler.GetHandler(procName); - - /// - /// Attempts to retrieve a function pointer from the DLL as the specified delegate type. Returns null if not found. - /// - /// The type of the delegate. - /// The name of the procedure to retrieve. - /// A delegate of type , or null if the procedure is not found. - protected T TryGetHandler(string procName) - where T : Delegate - => Handler.TryGetHandler(procName); - - /// - /// Disposes native resources associated with the DLL. - /// - protected override void DisposeNative() - { - Handler.FreeLibrary(); - base.DisposeNative(); - } + Handler.FreeLibrary(); + base.DisposeNative(); } } \ No newline at end of file diff --git a/Interop/GCHandle.cs b/Interop/GCHandle.cs index 5672d840..d6df7192 100644 --- a/Interop/GCHandle.cs +++ b/Interop/GCHandle.cs @@ -1,70 +1,69 @@ -namespace Ecng.Interop -{ - using System; - using System.Runtime.InteropServices; +namespace Ecng.Interop; + +using System; +using System.Runtime.InteropServices; + +using Ecng.Common; - using Ecng.Common; +/// +/// Generic version of structure . +/// +/// +/// +/// Initializes a new instance of the class. +/// +public class GCHandle(GCHandle handle) : Wrapper(handle) +{ + private readonly int? _size; /// - /// Generic version of structure . - /// - /// - /// /// Initializes a new instance of the class. - /// - public class GCHandle(GCHandle handle) : Wrapper(handle) + /// + /// The native value. + /// One of the values, indicating the type of to create. + public GCHandle(byte[] value, GCHandleType type = GCHandleType.Pinned) + : this(value.To(), type, value.Length) { - private readonly int? _size; - - /// - /// Initializes a new instance of the class. - /// - /// The native value. - /// One of the values, indicating the type of to create. - public GCHandle(byte[] value, GCHandleType type = GCHandleType.Pinned) - : this(value.To(), type, value.Length) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The native value. - /// One of the values, indicating the type of to create. - /// Size of the . - public GCHandle(T value, GCHandleType type, int? size) - : this(GCHandle.Alloc(value, type)) - { - if (size < 0) - throw new ArgumentOutOfRangeException(nameof(size)); + /// + /// Initializes a new instance of the class. + /// + /// The native value. + /// One of the values, indicating the type of to create. + /// Size of the . + public GCHandle(T value, GCHandleType type, int? size) + : this(GCHandle.Alloc(value, type)) + { + if (size < 0) + throw new ArgumentOutOfRangeException(nameof(size)); - _size = size; - } + _size = size; + } - /// - /// Create safe pointer. - /// - /// - public SafePointer CreatePointer() => new(Value.AddrOfPinnedObject(), _size); + /// + /// Create safe pointer. + /// + /// + public SafePointer CreatePointer() => new(Value.AddrOfPinnedObject(), _size); - /// - /// Disposes the native values. - /// - protected override void DisposeNative() - { - Value.Free(); - base.DisposeNative(); - } + /// + /// Disposes the native values. + /// + protected override void DisposeNative() + { + Value.Free(); + base.DisposeNative(); + } - /// - /// Creates a new object that is a copy of the current instance. - /// - /// - /// A new object that is a copy of this instance. - /// - public override Wrapper Clone() - { - throw new NotSupportedException(); - } + /// + /// Creates a new object that is a copy of the current instance. + /// + /// + /// A new object that is a copy of this instance. + /// + public override Wrapper Clone() + { + throw new NotSupportedException(); } } \ No newline at end of file diff --git a/Interop/HGlobalSafeHandle.cs b/Interop/HGlobalSafeHandle.cs index cdca5915..cfe6ec6e 100644 --- a/Interop/HGlobalSafeHandle.cs +++ b/Interop/HGlobalSafeHandle.cs @@ -1,32 +1,31 @@ -namespace Ecng.Interop -{ - using System; +namespace Ecng.Interop; + +using System; - using Microsoft.Win32.SafeHandles; +using Microsoft.Win32.SafeHandles; +/// +/// Represents a safe handle for unmanaged memory allocated with HGlobal. +/// +public class HGlobalSafeHandle : SafeHandleZeroOrMinusOneIsInvalid +{ /// - /// Represents a safe handle for unmanaged memory allocated with HGlobal. + /// Initializes a new instance of the class with the specified pointer. /// - public class HGlobalSafeHandle : SafeHandleZeroOrMinusOneIsInvalid + /// An that represents the allocated unmanaged memory. + public HGlobalSafeHandle(IntPtr ptr) + : base(true) { - /// - /// Initializes a new instance of the class with the specified pointer. - /// - /// An that represents the allocated unmanaged memory. - public HGlobalSafeHandle(IntPtr ptr) - : base(true) - { - SetHandle(ptr); - } + SetHandle(ptr); + } - /// - /// Releases the unmanaged memory by freeing the HGlobal allocation. - /// - /// true if the handle is released successfully; otherwise, false. - protected override bool ReleaseHandle() - { - DangerousGetHandle().FreeHGlobal(); - return true; - } + /// + /// Releases the unmanaged memory by freeing the HGlobal allocation. + /// + /// true if the handle is released successfully; otherwise, false. + protected override bool ReleaseHandle() + { + DangerousGetHandle().FreeHGlobal(); + return true; } } diff --git a/Interop/HardwareInfo.cs b/Interop/HardwareInfo.cs index a37acb41..da818959 100644 --- a/Interop/HardwareInfo.cs +++ b/Interop/HardwareInfo.cs @@ -1,132 +1,131 @@ -namespace Ecng.Interop -{ - using System; - using System.Linq; - using System.Collections.Generic; - using System.Net.NetworkInformation; - using System.Text.RegularExpressions; - using System.Threading; - using System.Threading.Tasks; +namespace Ecng.Interop; + +using System; +using System.Linq; +using System.Collections.Generic; +using System.Net.NetworkInformation; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; - using Ecng.Common; +using Ecng.Common; - using Nito.AsyncEx; +using Nito.AsyncEx; - using WmiLight; +using WmiLight; +/// +/// Provides methods to generate a hardware-based identifier. +/// +public static class HardwareInfo +{ /// - /// Provides methods to generate a hardware-based identifier. + /// Gets the hardware identifier. /// - public static class HardwareInfo + /// A string representing the hardware identifier. + public static string GetId() + => AsyncContext.Run(() => GetIdAsync()); + + /// + /// Asynchronously gets the hardware identifier. + /// + /// A token to monitor for cancellation requests. + /// A task that represents the asynchronous operation. The task result contains a string representing the hardware identifier. + public static async Task GetIdAsync(CancellationToken cancellationToken = default) + { + string id; + + if (OperatingSystemEx.IsWindows()) + id = await GetIdWindows(cancellationToken); + else + id = await GetIdLinuxAsync(cancellationToken); + + if (id.IsEmpty()) + throw new InvalidOperationException("Cannot generate HDDID."); + + return id; + } + + private static async Task GetWMIIdAsync(string table, string field, CancellationToken cancellationToken) + { + using var con = new WmiConnection(); + var query = con.CreateQuery($"Select * From {table}"); + var list = await Task.Run(() => query.ToArray(), cancellationToken); + return list.Select(o => (string)o[field]).FirstOrDefault(f => !f.IsEmptyOrWhiteSpace()); + } + + private static async Task GetIdWindows(CancellationToken cancellationToken) + { + var cpuid = await GetWMIIdAsync("Win32_processor", "ProcessorID", cancellationToken); + var mbId = await GetWMIIdAsync("Win32_BaseBoard", "SerialNumber", cancellationToken); + + if ( + mbId.EqualsIgnoreCase("none") || + mbId.EqualsIgnoreCase("n/a") || + mbId.EqualsIgnoreCase("invalid") || + mbId.EqualsIgnoreCase("To be filled by O.E.M.") || + mbId.EqualsIgnoreCase("Not Applicable") + ) + mbId = null; + + var netId = await GetWMIIdAsync("Win32_NetworkAdapter", "MACAddress", cancellationToken); + + if (mbId.IsEmpty() && netId.IsEmpty()) + throw new InvalidOperationException("MotherBoard and Network are both is empty."); + + return cpuid + (mbId.IsEmpty() ? netId : mbId); + } + + private static async Task GetIdLinuxAsync(CancellationToken cancellationToken) + { + var macs = GetLinuxMacs(); + var volId = await GetLinuxVolumeIdAsync(cancellationToken); + + return macs.Join(string.Empty) + volId; + } + + private static List GetLinuxMacs() { - /// - /// Gets the hardware identifier. - /// - /// A string representing the hardware identifier. - public static string GetId() - => AsyncContext.Run(() => GetIdAsync()); - - /// - /// Asynchronously gets the hardware identifier. - /// - /// A token to monitor for cancellation requests. - /// A task that represents the asynchronous operation. The task result contains a string representing the hardware identifier. - public static async Task GetIdAsync(CancellationToken cancellationToken = default) - { - string id; - - if (OperatingSystemEx.IsWindows()) - id = await GetIdWindows(cancellationToken); - else - id = await GetIdLinuxAsync(cancellationToken); - - if (id.IsEmpty()) - throw new InvalidOperationException("Cannot generate HDDID."); - - return id; - } - - private static async Task GetWMIIdAsync(string table, string field, CancellationToken cancellationToken) - { - using var con = new WmiConnection(); - var query = con.CreateQuery($"Select * From {table}"); - var list = await Task.Run(() => query.ToArray(), cancellationToken); - return list.Select(o => (string)o[field]).FirstOrDefault(f => !f.IsEmptyOrWhiteSpace()); - } - - private static async Task GetIdWindows(CancellationToken cancellationToken) - { - var cpuid = await GetWMIIdAsync("Win32_processor", "ProcessorID", cancellationToken); - var mbId = await GetWMIIdAsync("Win32_BaseBoard", "SerialNumber", cancellationToken); - - if ( - mbId.EqualsIgnoreCase("none") || - mbId.EqualsIgnoreCase("n/a") || - mbId.EqualsIgnoreCase("invalid") || - mbId.EqualsIgnoreCase("To be filled by O.E.M.") || - mbId.EqualsIgnoreCase("Not Applicable") - ) - mbId = null; - - var netId = await GetWMIIdAsync("Win32_NetworkAdapter", "MACAddress", cancellationToken); - - if (mbId.IsEmpty() && netId.IsEmpty()) - throw new InvalidOperationException("MotherBoard and Network are both is empty."); - - return cpuid + (mbId.IsEmpty() ? netId : mbId); - } - - private static async Task GetIdLinuxAsync(CancellationToken cancellationToken) - { - var macs = GetLinuxMacs(); - var volId = await GetLinuxVolumeIdAsync(cancellationToken); - - return macs.Join(string.Empty) + volId; - } - - private static List GetLinuxMacs() - { - var result = new HashSet(); - var ifaces = NetworkInterface - .GetAllNetworkInterfaces() - .Where(i => i.NetworkInterfaceType != NetworkInterfaceType.Loopback && - i.NetworkInterfaceType != NetworkInterfaceType.Tunnel && - i.OperationalStatus == OperationalStatus.Up); - - foreach (var iface in ifaces) - result.Add(iface.GetPhysicalAddress().ToString().ToLowerInvariant()); - - var list = result - .OrderBy(s => s) - .Where(s => s != "ffffffffffff" && s != "000000000000") - .ToList(); - - return list; - } - - private static readonly Regex _lsblkRegex = new(@"^\s*/\s+([\da-f-]+)\s*$", RegexOptions.Compiled | RegexOptions.IgnoreCase); - - private static async Task GetLinuxVolumeIdAsync(CancellationToken cancellationToken) - { - var errors = new List(); - var result = new List(); - - var res = await IOHelper.ExecuteAsync("lsblk", "-r -o MOUNTPOINT,UUID", str => - { - var m = _lsblkRegex.Match(str); - if (m.Success) - result.Add(m.Groups[1].Value); - }, - errStr => errors.Add(errStr), - info => info.EnvironmentVariables["PATH"] = "/bin:/sbin:/usr/bin:/usr/sbin", cancellationToken: cancellationToken); - - if (res != 0 || errors.Any()) - throw new InvalidOperationException($"Unable to execute lsblk. Return code {res}.\n{errors.JoinNL()}"); - - //if (result.Count != 1) - // throw new InvalidOperationException($"invalid lsblk result. got {result.Count} values: {result.JoinComma()}"); - - return result.FirstOrDefault()?.Remove("-").ToLowerInvariant(); - } + var result = new HashSet(); + var ifaces = NetworkInterface + .GetAllNetworkInterfaces() + .Where(i => i.NetworkInterfaceType != NetworkInterfaceType.Loopback && + i.NetworkInterfaceType != NetworkInterfaceType.Tunnel && + i.OperationalStatus == OperationalStatus.Up); + + foreach (var iface in ifaces) + result.Add(iface.GetPhysicalAddress().ToString().ToLowerInvariant()); + + var list = result + .OrderBy(s => s) + .Where(s => s != "ffffffffffff" && s != "000000000000") + .ToList(); + + return list; + } + + private static readonly Regex _lsblkRegex = new(@"^\s*/\s+([\da-f-]+)\s*$", RegexOptions.Compiled | RegexOptions.IgnoreCase); + + private static async Task GetLinuxVolumeIdAsync(CancellationToken cancellationToken) + { + var errors = new List(); + var result = new List(); + + var res = await IOHelper.ExecuteAsync("lsblk", "-r -o MOUNTPOINT,UUID", str => + { + var m = _lsblkRegex.Match(str); + if (m.Success) + result.Add(m.Groups[1].Value); + }, + errStr => errors.Add(errStr), + info => info.EnvironmentVariables["PATH"] = "/bin:/sbin:/usr/bin:/usr/sbin", cancellationToken: cancellationToken); + + if (res != 0 || errors.Any()) + throw new InvalidOperationException($"Unable to execute lsblk. Return code {res}.\n{errors.JoinNL()}"); + + //if (result.Count != 1) + // throw new InvalidOperationException($"invalid lsblk result. got {result.Count} values: {result.JoinComma()}"); + + return result.FirstOrDefault()?.Remove("-").ToLowerInvariant(); } } \ No newline at end of file diff --git a/Interop/IExcelWorker.cs b/Interop/IExcelWorker.cs index 6f88a5cf..d3fb1545 100644 --- a/Interop/IExcelWorker.cs +++ b/Interop/IExcelWorker.cs @@ -1,119 +1,118 @@ -namespace Ecng.Interop -{ - using System; - using System.IO; +namespace Ecng.Interop; + +using System; +using System.IO; - using Ecng.Common; +using Ecng.Common; +/// +/// Defines a contract for working with Excel files, providing methods to manipulate cells, styles, sheets, and formatting. +/// +public interface IExcelWorker : IDisposable +{ /// - /// Defines a contract for working with Excel files, providing methods to manipulate cells, styles, sheets, and formatting. + /// Sets the value of a cell at the specified column and row. /// - public interface IExcelWorker : IDisposable - { - /// - /// Sets the value of a cell at the specified column and row. - /// - /// The type of the value to set. - /// The column index (1-based). - /// The row index (1-based). - /// The value to set in the cell. - /// The current instance for method chaining. - IExcelWorker SetCell(int col, int row, T value); + /// The type of the value to set. + /// The column index (1-based). + /// The row index (1-based). + /// The value to set in the cell. + /// The current instance for method chaining. + IExcelWorker SetCell(int col, int row, T value); - /// - /// Gets the value of a cell at the specified column and row. - /// - /// The type of the value to retrieve. - /// The column index (1-based). - /// The row index (1-based). - /// The value of the cell cast to type . - T GetCell(int col, int row); + /// + /// Gets the value of a cell at the specified column and row. + /// + /// The type of the value to retrieve. + /// The column index (1-based). + /// The row index (1-based). + /// The value of the cell cast to type . + T GetCell(int col, int row); - /// - /// Sets the style of a column based on a specified type. - /// - /// The column index (1-based). - /// The that determines the style. - /// The current instance for method chaining. - IExcelWorker SetStyle(int col, Type type); + /// + /// Sets the style of a column based on a specified type. + /// + /// The column index (1-based). + /// The that determines the style. + /// The current instance for method chaining. + IExcelWorker SetStyle(int col, Type type); - /// - /// Sets the style of a column using a custom format string. - /// - /// The column index (1-based). - /// The format string to apply to the column. - /// The current instance for method chaining. - IExcelWorker SetStyle(int col, string format); + /// + /// Sets the style of a column using a custom format string. + /// + /// The column index (1-based). + /// The format string to apply to the column. + /// The current instance for method chaining. + IExcelWorker SetStyle(int col, string format); - /// - /// Sets conditional formatting for a column based on a condition. - /// - /// The column index (1-based). - /// The to use for the condition. - /// The condition value as a string. - /// The background color to apply if the condition is met (e.g., hex code or name). - /// The foreground (text) color to apply if the condition is met (e.g., hex code or name). - /// The current instance for method chaining. - IExcelWorker SetConditionalFormatting(int col, ComparisonOperator op, string condition, string bgColor, string fgColor); + /// + /// Sets conditional formatting for a column based on a condition. + /// + /// The column index (1-based). + /// The to use for the condition. + /// The condition value as a string. + /// The background color to apply if the condition is met (e.g., hex code or name). + /// The foreground (text) color to apply if the condition is met (e.g., hex code or name). + /// The current instance for method chaining. + IExcelWorker SetConditionalFormatting(int col, ComparisonOperator op, string condition, string bgColor, string fgColor); - /// - /// Renames the current sheet to the specified name. - /// - /// The new name for the sheet. - /// The current instance for method chaining. - IExcelWorker RenameSheet(string name); + /// + /// Renames the current sheet to the specified name. + /// + /// The new name for the sheet. + /// The current instance for method chaining. + IExcelWorker RenameSheet(string name); - /// - /// Adds a new sheet to the workbook. - /// - /// The current instance for method chaining. - IExcelWorker AddSheet(); + /// + /// Adds a new sheet to the workbook. + /// + /// The current instance for method chaining. + IExcelWorker AddSheet(); - /// - /// Checks if a sheet with the specified name exists in the workbook. - /// - /// The name of the sheet to check. - /// true if the sheet exists; otherwise, false. - bool ContainsSheet(string name); + /// + /// Checks if a sheet with the specified name exists in the workbook. + /// + /// The name of the sheet to check. + /// true if the sheet exists; otherwise, false. + bool ContainsSheet(string name); - /// - /// Switches the active sheet to the one with the specified name. - /// - /// The name of the sheet to switch to. - /// The current instance for method chaining. - IExcelWorker SwitchSheet(string name); + /// + /// Switches the active sheet to the one with the specified name. + /// + /// The name of the sheet to switch to. + /// The current instance for method chaining. + IExcelWorker SwitchSheet(string name); - /// - /// Gets the total number of columns in the current sheet. - /// - /// The number of columns. - int GetColumnsCount(); + /// + /// Gets the total number of columns in the current sheet. + /// + /// The number of columns. + int GetColumnsCount(); - /// - /// Gets the total number of rows in the current sheet. - /// - /// The number of rows. - int GetRowsCount(); - } + /// + /// Gets the total number of rows in the current sheet. + /// + /// The number of rows. + int GetRowsCount(); +} +/// +/// Defines a contract for creating and opening Excel worker instances from streams. +/// +public interface IExcelWorkerProvider +{ /// - /// Defines a contract for creating and opening Excel worker instances from streams. + /// Creates a new Excel workbook and returns an instance to interact with it. /// - public interface IExcelWorkerProvider - { - /// - /// Creates a new Excel workbook and returns an instance to interact with it. - /// - /// The stream to write the new workbook to. - /// If true, the workbook is opened in read-only mode; otherwise, it is writable. - /// An instance for the new workbook. - IExcelWorker CreateNew(Stream stream, bool readOnly = false); + /// The stream to write the new workbook to. + /// If true, the workbook is opened in read-only mode; otherwise, it is writable. + /// An instance for the new workbook. + IExcelWorker CreateNew(Stream stream, bool readOnly = false); - /// - /// Opens an existing Excel workbook from a stream and returns an instance to interact with it. - /// - /// The stream containing the existing workbook data. - /// An instance for the opened workbook. - IExcelWorker OpenExist(Stream stream); - } + /// + /// Opens an existing Excel workbook from a stream and returns an instance to interact with it. + /// + /// The stream containing the existing workbook data. + /// An instance for the opened workbook. + IExcelWorker OpenExist(Stream stream); } \ No newline at end of file diff --git a/Interop/Marshaler.cs b/Interop/Marshaler.cs index a7f61017..28eb4987 100644 --- a/Interop/Marshaler.cs +++ b/Interop/Marshaler.cs @@ -1,468 +1,467 @@ -namespace Ecng.Interop -{ - using System; - using System.ComponentModel; - using System.Text; - using System.Runtime.InteropServices; +namespace Ecng.Interop; + +using System; +using System.ComponentModel; +using System.Text; +using System.Runtime.InteropServices; - using Ecng.Common; +using Ecng.Common; #if NETCOREAPP - using CoreNativeLib = System.Runtime.InteropServices.NativeLibrary; +using CoreNativeLib = System.Runtime.InteropServices.NativeLibrary; #endif +/// +/// Provides a collection of extended methods that manipulate and extend the functionality of the class for interoperating with unmanaged memory and libraries. +/// +public static class Marshaler +{ /// - /// Provides a collection of extended methods that manipulate and extend the functionality of the class for interoperating with unmanaged memory and libraries. + /// Marshals data from an unmanaged block of memory to a newly allocated managed object of the specified type. /// - public static class Marshaler + /// The type of the structure to marshal. Must be a value type. + /// The pointer to the unmanaged block of memory. + /// A managed object of type containing the data from the unmanaged memory. + public static T ToStruct(this IntPtr ptr) + where T : struct { - /// - /// Marshals data from an unmanaged block of memory to a newly allocated managed object of the specified type. - /// - /// The type of the structure to marshal. Must be a value type. - /// The pointer to the unmanaged block of memory. - /// A managed object of type containing the data from the unmanaged memory. - public static T ToStruct(this IntPtr ptr) - where T : struct - { - return (T)Marshal.PtrToStructure(ptr, typeof(T)); - } + return (T)Marshal.PtrToStructure(ptr, typeof(T)); + } - /// - /// Marshals data from a managed object to an unmanaged block of memory and returns the pointer. - /// - /// The type of the structure to marshal. Must be a value type. - /// The managed object to marshal. - /// The optional size of the unmanaged memory block. If null, the size of is used. - /// A pointer to the allocated unmanaged memory containing the marshaled data. - public static IntPtr StructToPtr(this T structure, int? size = default) - where T : struct - => structure.StructToPtrEx(size).ptr; - - /// - /// Marshals data from a managed object to an unmanaged block of memory and returns the pointer along with the size. - /// - /// The type of the structure to marshal. Must be a value type. - /// The managed object to marshal. - /// The optional size of the unmanaged memory block. If null, the size of is used. - /// A tuple containing the pointer to the unmanaged memory and its size in bytes. - public static (IntPtr ptr, int size) StructToPtrEx(this T structure, int? size = default) - where T : struct - { - size ??= typeof(T).SizeOf(); - var ptr = Marshal.AllocHGlobal(size.Value); - Marshal.StructureToPtr(structure, ptr, false); - return (ptr, size.Value); - } + /// + /// Marshals data from a managed object to an unmanaged block of memory and returns the pointer. + /// + /// The type of the structure to marshal. Must be a value type. + /// The managed object to marshal. + /// The optional size of the unmanaged memory block. If null, the size of is used. + /// A pointer to the allocated unmanaged memory containing the marshaled data. + public static IntPtr StructToPtr(this T structure, int? size = default) + where T : struct + => structure.StructToPtrEx(size).ptr; - /// - /// Writes a value to the specified unmanaged memory location. - /// - /// The type of the value to write. Must be a supported primitive type (e.g., byte, short, int, long, IntPtr). - /// The address in unmanaged memory to write to. - /// The value to write. - /// Thrown when is not a supported type. - public static void Write(this IntPtr ptr, T value) - where T : struct - { - if (typeof(T) == typeof(byte) || typeof(T) == typeof(sbyte)) - Marshal.WriteByte(ptr, value.To()); - else if (typeof(T) == typeof(short) || typeof(T) == typeof(ushort)) - Marshal.WriteInt16(ptr, value.To()); - else if (typeof(T) == typeof(int) || typeof(T) == typeof(uint)) - Marshal.WriteInt32(ptr, value.To()); - else if (typeof(T) == typeof(long) || typeof(T) == typeof(ulong)) - Marshal.WriteInt64(ptr, value.To()); - else if (typeof(T) == typeof(IntPtr) || typeof(T) == typeof(UIntPtr)) - Marshal.WriteIntPtr(ptr, value.To()); - else - throw new ArgumentException(typeof(T).Name, nameof(value)); - } + /// + /// Marshals data from a managed object to an unmanaged block of memory and returns the pointer along with the size. + /// + /// The type of the structure to marshal. Must be a value type. + /// The managed object to marshal. + /// The optional size of the unmanaged memory block. If null, the size of is used. + /// A tuple containing the pointer to the unmanaged memory and its size in bytes. + public static (IntPtr ptr, int size) StructToPtrEx(this T structure, int? size = default) + where T : struct + { + size ??= typeof(T).SizeOf(); + var ptr = Marshal.AllocHGlobal(size.Value); + Marshal.StructureToPtr(structure, ptr, false); + return (ptr, size.Value); + } - /// - /// Reads a value from the specified unmanaged memory location. - /// - /// The type of the value to read. Must be a supported primitive type (e.g., byte, short, int, long, IntPtr). - /// The address in unmanaged memory to read from. - /// The value read from the unmanaged memory, cast to type . - /// Thrown when is not a supported type. - public static T Read(this IntPtr ptr) - where T : struct - { - object retVal; - - if (typeof(T) == typeof(byte) || typeof(T) == typeof(sbyte)) - retVal = Marshal.ReadByte(ptr); - else if (typeof(T) == typeof(short) || typeof(T) == typeof(ushort)) - retVal = Marshal.ReadInt16(ptr); - else if (typeof(T) == typeof(int) || typeof(T) == typeof(uint)) - retVal = Marshal.ReadInt32(ptr); - else if (typeof(T) == typeof(long) || typeof(T) == typeof(ulong)) - retVal = Marshal.ReadInt64(ptr); - else if (typeof(T) == typeof(IntPtr) || typeof(T) == typeof(UIntPtr)) - retVal = Marshal.ReadIntPtr(ptr); - else - throw new ArgumentException(typeof(T).Name, nameof(ptr)); - - return retVal.To(); - } + /// + /// Writes a value to the specified unmanaged memory location. + /// + /// The type of the value to write. Must be a supported primitive type (e.g., byte, short, int, long, IntPtr). + /// The address in unmanaged memory to write to. + /// The value to write. + /// Thrown when is not a supported type. + public static void Write(this IntPtr ptr, T value) + where T : struct + { + if (typeof(T) == typeof(byte) || typeof(T) == typeof(sbyte)) + Marshal.WriteByte(ptr, value.To()); + else if (typeof(T) == typeof(short) || typeof(T) == typeof(ushort)) + Marshal.WriteInt16(ptr, value.To()); + else if (typeof(T) == typeof(int) || typeof(T) == typeof(uint)) + Marshal.WriteInt32(ptr, value.To()); + else if (typeof(T) == typeof(long) || typeof(T) == typeof(ulong)) + Marshal.WriteInt64(ptr, value.To()); + else if (typeof(T) == typeof(IntPtr) || typeof(T) == typeof(UIntPtr)) + Marshal.WriteIntPtr(ptr, value.To()); + else + throw new ArgumentException(typeof(T).Name, nameof(value)); + } - /// - /// Converts an unmanaged function pointer to a delegate of the specified type. - /// - /// The type of the delegate to create. - /// The unmanaged function pointer to convert. - /// A delegate of type that wraps the unmanaged function. - public static T GetDelegateForFunctionPointer(this IntPtr ptr) - { - return Marshal.GetDelegateForFunctionPointer(ptr, typeof(T)).To(); - } + /// + /// Reads a value from the specified unmanaged memory location. + /// + /// The type of the value to read. Must be a supported primitive type (e.g., byte, short, int, long, IntPtr). + /// The address in unmanaged memory to read from. + /// The value read from the unmanaged memory, cast to type . + /// Thrown when is not a supported type. + public static T Read(this IntPtr ptr) + where T : struct + { + object retVal; + + if (typeof(T) == typeof(byte) || typeof(T) == typeof(sbyte)) + retVal = Marshal.ReadByte(ptr); + else if (typeof(T) == typeof(short) || typeof(T) == typeof(ushort)) + retVal = Marshal.ReadInt16(ptr); + else if (typeof(T) == typeof(int) || typeof(T) == typeof(uint)) + retVal = Marshal.ReadInt32(ptr); + else if (typeof(T) == typeof(long) || typeof(T) == typeof(ulong)) + retVal = Marshal.ReadInt64(ptr); + else if (typeof(T) == typeof(IntPtr) || typeof(T) == typeof(UIntPtr)) + retVal = Marshal.ReadIntPtr(ptr); + else + throw new ArgumentException(typeof(T).Name, nameof(ptr)); + + return retVal.To(); + } - /// - /// Converts an unmanaged ANSI string pointer to a managed string. - /// - /// The pointer to the ANSI string in unmanaged memory. - /// The managed string representation of the ANSI string. - public static string ToAnsi(this IntPtr ptr) => Marshal.PtrToStringAnsi(ptr); - - /// - /// Converts an unmanaged ANSI string pointer to a managed string with a specified length. - /// - /// The pointer to the ANSI string in unmanaged memory. - /// The length of the string to read. - /// The managed string representation of the ANSI string. - public static string ToAnsi(this IntPtr ptr, int len) => Marshal.PtrToStringAnsi(ptr, len); - - /// - /// Converts a managed string to an unmanaged ANSI string and returns a pointer to it. - /// - /// The managed string to convert. - /// A pointer to the unmanaged ANSI string. - public static IntPtr FromAnsi(this string str) => Marshal.StringToHGlobalAnsi(str); - - /// - /// Converts an unmanaged string pointer (platform-dependent encoding) to a managed string. - /// - /// The pointer to the string in unmanaged memory. - /// The managed string representation. - public static string ToAuto(this IntPtr ptr) => Marshal.PtrToStringAuto(ptr); - - /// - /// Converts an unmanaged string pointer (platform-dependent encoding) to a managed string with a specified length. - /// - /// The pointer to the string in unmanaged memory. - /// The length of the string to read. - /// The managed string representation. - public static string ToAuto(this IntPtr ptr, int len) => Marshal.PtrToStringAuto(ptr, len); - - /// - /// Converts a managed string to an unmanaged string (platform-dependent encoding) and returns a pointer to it. - /// - /// The managed string to convert. - /// A pointer to the unmanaged string. - public static IntPtr FromAuto(this string str) => Marshal.StringToHGlobalAuto(str); - - /// - /// Converts an unmanaged BSTR pointer to a managed string. - /// - /// The pointer to the BSTR in unmanaged memory. - /// The managed string representation of the BSTR. - public static string ToBSTR(this IntPtr ptr) => Marshal.PtrToStringBSTR(ptr); - - /// - /// Converts a managed string to an unmanaged BSTR and returns a pointer to it. - /// - /// The managed string to convert. - /// A pointer to the unmanaged BSTR. - public static IntPtr FromBSTR(this string str) => Marshal.StringToBSTR(str); - - /// - /// Converts an unmanaged Unicode string pointer to a managed string. - /// - /// The pointer to the Unicode string in unmanaged memory. - /// The managed string representation of the Unicode string. - public static string ToUnicode(this IntPtr ptr) => Marshal.PtrToStringUni(ptr); - - /// - /// Converts an unmanaged Unicode string pointer to a managed string with a specified length. - /// - /// The pointer to the Unicode string in unmanaged memory. - /// The length of the string to read. - /// The managed string representation of the Unicode string. - public static string ToUnicode(this IntPtr ptr, int len) => Marshal.PtrToStringUni(ptr, len); - - /// - /// Converts a managed string to an unmanaged Unicode string and returns a pointer to it. - /// - /// The managed string to convert. - /// A pointer to the unmanaged Unicode string. - public static IntPtr FromUnicode(this string str) => Marshal.StringToHGlobalUni(str); - - /// - /// Allocates unmanaged memory of the specified size and wraps it in a safe handle. - /// - /// The size of the memory to allocate, interpreted as an integer. - /// A wrapping the allocated unmanaged memory. - public static HGlobalSafeHandle ToHGlobal(this int ptr) - { - return ((IntPtr)ptr).ToHGlobal(); - } + /// + /// Converts an unmanaged function pointer to a delegate of the specified type. + /// + /// The type of the delegate to create. + /// The unmanaged function pointer to convert. + /// A delegate of type that wraps the unmanaged function. + public static T GetDelegateForFunctionPointer(this IntPtr ptr) + { + return Marshal.GetDelegateForFunctionPointer(ptr, typeof(T)).To(); + } - /// - /// Allocates unmanaged memory of the specified size and wraps it in a safe handle. - /// - /// The size of the memory to allocate, as an . - /// A wrapping the allocated unmanaged memory. - public static HGlobalSafeHandle ToHGlobal(this IntPtr ptr) - { - return new HGlobalSafeHandle(Marshal.AllocHGlobal(ptr)); - } + /// + /// Converts an unmanaged ANSI string pointer to a managed string. + /// + /// The pointer to the ANSI string in unmanaged memory. + /// The managed string representation of the ANSI string. + public static string ToAnsi(this IntPtr ptr) => Marshal.PtrToStringAnsi(ptr); - /// - /// Encodes a string using the specified encoding, allocates unmanaged memory for it, and returns a safe handle. - /// - /// The encoding to use for the string. - /// The string to encode and allocate. - /// A wrapping the allocated unmanaged memory containing the encoded string. - /// Thrown when is null. - public static HGlobalSafeHandle ToHGlobal(this Encoding encoding, string data) - { - if (encoding is null) - throw new ArgumentNullException(nameof(encoding)); + /// + /// Converts an unmanaged ANSI string pointer to a managed string with a specified length. + /// + /// The pointer to the ANSI string in unmanaged memory. + /// The length of the string to read. + /// The managed string representation of the ANSI string. + public static string ToAnsi(this IntPtr ptr, int len) => Marshal.PtrToStringAnsi(ptr, len); - var dataEncoded = encoding.GetBytes(data); - var size = typeof(byte).SizeOf() * dataEncoded.Length; - var pData = size.ToHGlobal(); + /// + /// Converts a managed string to an unmanaged ANSI string and returns a pointer to it. + /// + /// The managed string to convert. + /// A pointer to the unmanaged ANSI string. + public static IntPtr FromAnsi(this string str) => Marshal.StringToHGlobalAnsi(str); - Marshal.Copy(dataEncoded, 0, pData.DangerousGetHandle(), dataEncoded.Length); + /// + /// Converts an unmanaged string pointer (platform-dependent encoding) to a managed string. + /// + /// The pointer to the string in unmanaged memory. + /// The managed string representation. + public static string ToAuto(this IntPtr ptr) => Marshal.PtrToStringAuto(ptr); - return pData; - } + /// + /// Converts an unmanaged string pointer (platform-dependent encoding) to a managed string with a specified length. + /// + /// The pointer to the string in unmanaged memory. + /// The length of the string to read. + /// The managed string representation. + public static string ToAuto(this IntPtr ptr, int len) => Marshal.PtrToStringAuto(ptr, len); - /// - /// Decodes an unmanaged ANSI string from a pointer into a managed string using the specified encoding. - /// - /// The encoding to use for decoding the string. - /// The pointer to the ANSI string in unmanaged memory. - /// The decoded managed string. - /// Thrown when is null. - public static string ToString(this Encoding encoding, IntPtr pData) - { - if (encoding is null) - throw new ArgumentNullException(nameof(encoding)); + /// + /// Converts a managed string to an unmanaged string (platform-dependent encoding) and returns a pointer to it. + /// + /// The managed string to convert. + /// A pointer to the unmanaged string. + public static IntPtr FromAuto(this string str) => Marshal.StringToHGlobalAuto(str); - var errStr = pData.ToAnsi(); - var length = errStr.Length; + /// + /// Converts an unmanaged BSTR pointer to a managed string. + /// + /// The pointer to the BSTR in unmanaged memory. + /// The managed string representation of the BSTR. + public static string ToBSTR(this IntPtr ptr) => Marshal.PtrToStringBSTR(ptr); - var data = new byte[length]; - Marshal.Copy(pData, data, 0, length); + /// + /// Converts a managed string to an unmanaged BSTR and returns a pointer to it. + /// + /// The managed string to convert. + /// A pointer to the unmanaged BSTR. + public static IntPtr FromBSTR(this string str) => Marshal.StringToBSTR(str); - return encoding.GetString(data); - } + /// + /// Converts an unmanaged Unicode string pointer to a managed string. + /// + /// The pointer to the Unicode string in unmanaged memory. + /// The managed string representation of the Unicode string. + public static string ToUnicode(this IntPtr ptr) => Marshal.PtrToStringUni(ptr); - /// - /// Retrieves a delegate for a named procedure from an unmanaged library. - /// - /// The type of the delegate to retrieve. - /// The handle to the loaded unmanaged library. - /// The name of the procedure to retrieve. - /// A delegate of type for the specified procedure. - /// Thrown when the procedure cannot be found in the library. - public static T GetHandler(this IntPtr library, string procName) - => GetDelegateForFunctionPointer(GetProcAddress(library, procName)); - - /// - /// Attempts to retrieve a delegate for a named procedure from an unmanaged library. - /// - /// The type of the delegate to retrieve. Must inherit from . - /// The handle to the loaded unmanaged library. - /// The name of the procedure to retrieve. - /// A delegate of type if found; otherwise, null. - public static T TryGetHandler(this IntPtr library, string procName) - where T : Delegate - { - if (TryGetProcAddress(library, procName, out var address)) - return GetDelegateForFunctionPointer(address); + /// + /// Converts an unmanaged Unicode string pointer to a managed string with a specified length. + /// + /// The pointer to the Unicode string in unmanaged memory. + /// The length of the string to read. + /// The managed string representation of the Unicode string. + public static string ToUnicode(this IntPtr ptr, int len) => Marshal.PtrToStringUni(ptr, len); - return null; - } + /// + /// Converts a managed string to an unmanaged Unicode string and returns a pointer to it. + /// + /// The managed string to convert. + /// A pointer to the unmanaged Unicode string. + public static IntPtr FromUnicode(this string str) => Marshal.StringToHGlobalUni(str); + + /// + /// Allocates unmanaged memory of the specified size and wraps it in a safe handle. + /// + /// The size of the memory to allocate, interpreted as an integer. + /// A wrapping the allocated unmanaged memory. + public static HGlobalSafeHandle ToHGlobal(this int ptr) + { + return ((IntPtr)ptr).ToHGlobal(); + } + + /// + /// Allocates unmanaged memory of the specified size and wraps it in a safe handle. + /// + /// The size of the memory to allocate, as an . + /// A wrapping the allocated unmanaged memory. + public static HGlobalSafeHandle ToHGlobal(this IntPtr ptr) + { + return new HGlobalSafeHandle(Marshal.AllocHGlobal(ptr)); + } + + /// + /// Encodes a string using the specified encoding, allocates unmanaged memory for it, and returns a safe handle. + /// + /// The encoding to use for the string. + /// The string to encode and allocate. + /// A wrapping the allocated unmanaged memory containing the encoded string. + /// Thrown when is null. + public static HGlobalSafeHandle ToHGlobal(this Encoding encoding, string data) + { + if (encoding is null) + throw new ArgumentNullException(nameof(encoding)); + + var dataEncoded = encoding.GetBytes(data); + var size = typeof(byte).SizeOf() * dataEncoded.Length; + var pData = size.ToHGlobal(); + + Marshal.Copy(dataEncoded, 0, pData.DangerousGetHandle(), dataEncoded.Length); + + return pData; + } + + /// + /// Decodes an unmanaged ANSI string from a pointer into a managed string using the specified encoding. + /// + /// The encoding to use for decoding the string. + /// The pointer to the ANSI string in unmanaged memory. + /// The decoded managed string. + /// Thrown when is null. + public static string ToString(this Encoding encoding, IntPtr pData) + { + if (encoding is null) + throw new ArgumentNullException(nameof(encoding)); + + var errStr = pData.ToAnsi(); + var length = errStr.Length; + + var data = new byte[length]; + Marshal.Copy(pData, data, 0, length); + + return encoding.GetString(data); + } + + /// + /// Retrieves a delegate for a named procedure from an unmanaged library. + /// + /// The type of the delegate to retrieve. + /// The handle to the loaded unmanaged library. + /// The name of the procedure to retrieve. + /// A delegate of type for the specified procedure. + /// Thrown when the procedure cannot be found in the library. + public static T GetHandler(this IntPtr library, string procName) + => GetDelegateForFunctionPointer(GetProcAddress(library, procName)); + + /// + /// Attempts to retrieve a delegate for a named procedure from an unmanaged library. + /// + /// The type of the delegate to retrieve. Must inherit from . + /// The handle to the loaded unmanaged library. + /// The name of the procedure to retrieve. + /// A delegate of type if found; otherwise, null. + public static T TryGetHandler(this IntPtr library, string procName) + where T : Delegate + { + if (TryGetProcAddress(library, procName, out var address)) + return GetDelegateForFunctionPointer(address); + + return null; + } #if !NETCOREAPP - [DllImport("kernel32", SetLastError = true, CallingConvention = CallingConvention.StdCall, EntryPoint = "LoadLibrary")] - private static extern IntPtr Kernel32LoadLibrary([In] string dllname); + [DllImport("kernel32", SetLastError = true, CallingConvention = CallingConvention.StdCall, EntryPoint = "LoadLibrary")] + private static extern IntPtr Kernel32LoadLibrary([In] string dllname); - [DllImport("kernel32", SetLastError = true, CallingConvention = CallingConvention.StdCall, EntryPoint = "FreeLibrary")] - private static extern bool Kernel32FreeLibrary([In] IntPtr hModule); + [DllImport("kernel32", SetLastError = true, CallingConvention = CallingConvention.StdCall, EntryPoint = "FreeLibrary")] + private static extern bool Kernel32FreeLibrary([In] IntPtr hModule); - [DllImport("kernel32", SetLastError = true, CallingConvention = CallingConvention.StdCall, EntryPoint = "GetProcAddress")] - private static extern IntPtr Kernel32GetProcAddress([In] IntPtr hModule, [In] string procName); + [DllImport("kernel32", SetLastError = true, CallingConvention = CallingConvention.StdCall, EntryPoint = "GetProcAddress")] + private static extern IntPtr Kernel32GetProcAddress([In] IntPtr hModule, [In] string procName); #endif - /// - /// Loads an unmanaged library from the specified path. - /// - /// The file path to the unmanaged library (DLL). - /// A handle to the loaded library. - /// Thrown when is null or empty. - /// Thrown when the library cannot be loaded. - public static IntPtr LoadLibrary(string dllPath) - { - if (dllPath.IsEmpty()) - throw new ArgumentNullException(nameof(dllPath)); + /// + /// Loads an unmanaged library from the specified path. + /// + /// The file path to the unmanaged library (DLL). + /// A handle to the loaded library. + /// Thrown when is null or empty. + /// Thrown when the library cannot be loaded. + public static IntPtr LoadLibrary(string dllPath) + { + if (dllPath.IsEmpty()) + throw new ArgumentNullException(nameof(dllPath)); #if NETCOREAPP - var handler = CoreNativeLib.Load(dllPath); + var handler = CoreNativeLib.Load(dllPath); #else - var handler = Kernel32LoadLibrary(dllPath); + var handler = Kernel32LoadLibrary(dllPath); #endif - if (handler == IntPtr.Zero) - throw new ArgumentException($"Error load library '{dllPath}'.", nameof(dllPath), new Win32Exception()); + if (handler == IntPtr.Zero) + throw new ArgumentException($"Error load library '{dllPath}'.", nameof(dllPath), new Win32Exception()); - return handler; - } + return handler; + } - /// - /// Frees a previously loaded unmanaged library. - /// - /// The handle to the library to free. - /// true if the library was successfully freed; otherwise, false. - public static bool FreeLibrary(this IntPtr hModule) - { + /// + /// Frees a previously loaded unmanaged library. + /// + /// The handle to the library to free. + /// true if the library was successfully freed; otherwise, false. + public static bool FreeLibrary(this IntPtr hModule) + { #if NETCOREAPP - CoreNativeLib.Free(hModule); - return true; + CoreNativeLib.Free(hModule); + return true; #else - return Kernel32FreeLibrary(hModule); + return Kernel32FreeLibrary(hModule); #endif - } + } - /// - /// Retrieves the address of a named procedure from an unmanaged library. - /// - /// The handle to the loaded unmanaged library. - /// The name of the procedure to retrieve. - /// The address of the procedure in unmanaged memory. - /// Thrown when the procedure cannot be found in the library. - public static IntPtr GetProcAddress(this IntPtr hModule, string procName) - { - if (TryGetProcAddress(hModule, procName, out var addr)) - return addr; + /// + /// Retrieves the address of a named procedure from an unmanaged library. + /// + /// The handle to the loaded unmanaged library. + /// The name of the procedure to retrieve. + /// The address of the procedure in unmanaged memory. + /// Thrown when the procedure cannot be found in the library. + public static IntPtr GetProcAddress(this IntPtr hModule, string procName) + { + if (TryGetProcAddress(hModule, procName, out var addr)) + return addr; - throw new ArgumentException($"Error load procedure {procName}.", nameof(procName), new Win32Exception()); - } + throw new ArgumentException($"Error load procedure {procName}.", nameof(procName), new Win32Exception()); + } - /// - /// Attempts to retrieve the address of a named procedure from an unmanaged library. - /// - /// The handle to the loaded unmanaged library. - /// The name of the procedure to retrieve. - /// When this method returns, contains the address of the procedure if found; otherwise, . - /// true if the procedure address was found; otherwise, false. - public static bool TryGetProcAddress(this IntPtr hModule, string procName, out IntPtr address) - { + /// + /// Attempts to retrieve the address of a named procedure from an unmanaged library. + /// + /// The handle to the loaded unmanaged library. + /// The name of the procedure to retrieve. + /// When this method returns, contains the address of the procedure if found; otherwise, . + /// true if the procedure address was found; otherwise, false. + public static bool TryGetProcAddress(this IntPtr hModule, string procName, out IntPtr address) + { #if NETCOREAPP - return CoreNativeLib.TryGetExport(hModule, procName, out address); + return CoreNativeLib.TryGetExport(hModule, procName, out address); #else - address = Kernel32GetProcAddress(hModule, procName); - return address != IntPtr.Zero; + address = Kernel32GetProcAddress(hModule, procName); + return address != IntPtr.Zero; #endif - } - - /// - /// Converts an unmanaged byte reference to a managed string using the specified encoding, with a maximum byte length. - /// - /// The encoding to use for decoding the string. - /// A reference to the starting byte in unmanaged memory. - /// The maximum number of bytes to read. - /// The decoded managed string, or null if the source is zero. - /// Thrown when is null. - /// Thrown when is negative. - public static unsafe string GetUnsafeString(this Encoding encoding, ref byte srcChar, int maxBytes) - { - if (encoding is null) - throw new ArgumentNullException(nameof(encoding)); + } - if (maxBytes < 0) - throw new ArgumentOutOfRangeException(nameof(maxBytes)); + /// + /// Converts an unmanaged byte reference to a managed string using the specified encoding, with a maximum byte length. + /// + /// The encoding to use for decoding the string. + /// A reference to the starting byte in unmanaged memory. + /// The maximum number of bytes to read. + /// The decoded managed string, or null if the source is zero. + /// Thrown when is null. + /// Thrown when is negative. + public static unsafe string GetUnsafeString(this Encoding encoding, ref byte srcChar, int maxBytes) + { + if (encoding is null) + throw new ArgumentNullException(nameof(encoding)); - if (srcChar == 0) - return null; + if (maxBytes < 0) + throw new ArgumentOutOfRangeException(nameof(maxBytes)); - var charBuffer = stackalloc char[maxBytes]; + if (srcChar == 0) + return null; - fixed (byte* ptr8 = &srcChar) - { - encoding.GetChars(ptr8, maxBytes, charBuffer, maxBytes); - return new string(charBuffer); - } - } + var charBuffer = stackalloc char[maxBytes]; - /// - /// Writes a managed string to an unmanaged byte reference using the specified encoding, with a maximum byte length. - /// - /// The encoding to use for encoding the string. - /// A reference to the target byte in unmanaged memory. - /// The maximum number of bytes to write. - /// The string to write. - /// Thrown when is null. - /// - /// Thrown when is negative or the string length exceeds . - /// - public static unsafe void SetUnsafeString(this Encoding encoding, ref byte tgtChar, int maxBytes, string value) + fixed (byte* ptr8 = &srcChar) { - if (encoding is null) - throw new ArgumentNullException(nameof(encoding)); + encoding.GetChars(ptr8, maxBytes, charBuffer, maxBytes); + return new string(charBuffer); + } + } - if (maxBytes < 0) - throw new ArgumentOutOfRangeException(nameof(maxBytes)); + /// + /// Writes a managed string to an unmanaged byte reference using the specified encoding, with a maximum byte length. + /// + /// The encoding to use for encoding the string. + /// A reference to the target byte in unmanaged memory. + /// The maximum number of bytes to write. + /// The string to write. + /// Thrown when is null. + /// + /// Thrown when is negative or the string length exceeds . + /// + public static unsafe void SetUnsafeString(this Encoding encoding, ref byte tgtChar, int maxBytes, string value) + { + if (encoding is null) + throw new ArgumentNullException(nameof(encoding)); - if (value.IsEmpty()) - return; + if (maxBytes < 0) + throw new ArgumentOutOfRangeException(nameof(maxBytes)); - if (value.Length >= maxBytes) - throw new ArgumentOutOfRangeException(nameof(maxBytes), maxBytes, "Invalid value."); + if (value.IsEmpty()) + return; - var charBuffer = stackalloc char[maxBytes]; + if (value.Length >= maxBytes) + throw new ArgumentOutOfRangeException(nameof(maxBytes), maxBytes, "Invalid value."); - fixed (byte* ptr8 = &tgtChar) - { - for (var b = 0; b < maxBytes; b++) - ptr8[b] = 0; + var charBuffer = stackalloc char[maxBytes]; - for (var c = 0; c < value.Length; c++) - charBuffer[c] = value[c]; + fixed (byte* ptr8 = &tgtChar) + { + for (var b = 0; b < maxBytes; b++) + ptr8[b] = 0; - encoding.GetBytes(charBuffer, value.Length, ptr8, maxBytes); - } - } + for (var c = 0; c < value.Length; c++) + charBuffer[c] = value[c]; - /// - /// Copies data from an unmanaged pointer to a byte array. - /// - /// The pointer to the unmanaged memory to copy from. - /// The byte array to copy the data into. - public static void CopyTo(this IntPtr ptr, byte[] buffer) - { - ptr.CopyTo(buffer, 0, buffer.Length); + encoding.GetBytes(charBuffer, value.Length, ptr8, maxBytes); } + } - /// - /// Copies a specified amount of data from an unmanaged pointer to a byte array. - /// - /// The pointer to the unmanaged memory to copy from. - /// The byte array to copy the data into. - /// The starting index in the buffer where data should be copied. - /// The number of bytes to copy. - public static void CopyTo(this IntPtr ptr, byte[] buffer, int offset, int length) - { - Marshal.Copy(ptr, buffer, offset, length); - } + /// + /// Copies data from an unmanaged pointer to a byte array. + /// + /// The pointer to the unmanaged memory to copy from. + /// The byte array to copy the data into. + public static void CopyTo(this IntPtr ptr, byte[] buffer) + { + ptr.CopyTo(buffer, 0, buffer.Length); + } - /// - /// Frees an unmanaged memory block previously allocated with . - /// - /// The pointer to the unmanaged memory to free. - public static void FreeHGlobal(this IntPtr ptr) => Marshal.FreeHGlobal(ptr); + /// + /// Copies a specified amount of data from an unmanaged pointer to a byte array. + /// + /// The pointer to the unmanaged memory to copy from. + /// The byte array to copy the data into. + /// The starting index in the buffer where data should be copied. + /// The number of bytes to copy. + public static void CopyTo(this IntPtr ptr, byte[] buffer, int offset, int length) + { + Marshal.Copy(ptr, buffer, offset, length); } + + /// + /// Frees an unmanaged memory block previously allocated with . + /// + /// The pointer to the unmanaged memory to free. + public static void FreeHGlobal(this IntPtr ptr) => Marshal.FreeHGlobal(ptr); } \ No newline at end of file diff --git a/Interop/PtrReader.cs b/Interop/PtrReader.cs index aa71c96c..8a471a48 100644 --- a/Interop/PtrReader.cs +++ b/Interop/PtrReader.cs @@ -1,125 +1,124 @@ -namespace Ecng.Interop -{ - using System; - using System.Runtime.InteropServices; +namespace Ecng.Interop; + +using System; +using System.Runtime.InteropServices; +/// +/// Provides methods to read data from an unmanaged memory pointer. +/// +public class PtrReader +{ /// - /// Provides methods to read data from an unmanaged memory pointer. + /// Initializes a new instance of the class with the specified pointer. /// - public class PtrReader + /// The unmanaged memory pointer to read from. + /// Thrown if is . + public PtrReader(IntPtr ptr) { - /// - /// Initializes a new instance of the class with the specified pointer. - /// - /// The unmanaged memory pointer to read from. - /// Thrown if is . - public PtrReader(IntPtr ptr) - { - Ptr = ptr; - } + Ptr = ptr; + } - private IntPtr _ptr; + private IntPtr _ptr; - /// - /// Gets or sets the unmanaged memory pointer. - /// - /// Thrown if the value is . - public IntPtr Ptr + /// + /// Gets or sets the unmanaged memory pointer. + /// + /// Thrown if the value is . + public IntPtr Ptr + { + get { return _ptr; } + set { - get { return _ptr; } - set - { - if (value == IntPtr.Zero) - throw new ArgumentNullException(nameof(value)); - - _ptr = value; - } + if (value == IntPtr.Zero) + throw new ArgumentNullException(nameof(value)); + + _ptr = value; } + } - /// - /// Reads a byte from the current pointer and advances the pointer by the size of a byte. - /// - /// The byte read from the pointer. - public byte GetByte() - { - var ret = Marshal.ReadByte(_ptr); - _ptr += sizeof(byte); + /// + /// Reads a byte from the current pointer and advances the pointer by the size of a byte. + /// + /// The byte read from the pointer. + public byte GetByte() + { + var ret = Marshal.ReadByte(_ptr); + _ptr += sizeof(byte); - return ret; - } + return ret; + } - /// - /// Reads a 32-bit integer from the current pointer and advances the pointer by the size of an integer. - /// - /// The 32-bit integer read from the pointer. - public int GetInt() - { - var ret = Marshal.ReadInt32(_ptr); - _ptr += sizeof(int); + /// + /// Reads a 32-bit integer from the current pointer and advances the pointer by the size of an integer. + /// + /// The 32-bit integer read from the pointer. + public int GetInt() + { + var ret = Marshal.ReadInt32(_ptr); + _ptr += sizeof(int); - return ret; - } + return ret; + } - /// - /// Reads a 64-bit integer from the current pointer and advances the pointer by the size of a long. - /// - /// The 64-bit integer read from the pointer. - public long GetLong() - { - var ret = Marshal.ReadInt64(_ptr); - _ptr += sizeof(long); + /// + /// Reads a 64-bit integer from the current pointer and advances the pointer by the size of a long. + /// + /// The 64-bit integer read from the pointer. + public long GetLong() + { + var ret = Marshal.ReadInt64(_ptr); + _ptr += sizeof(long); - return ret; - } + return ret; + } - /// - /// Reads a 16-bit integer from the current pointer and advances the pointer by the size of a short. - /// - /// The 16-bit integer read from the pointer. - public short GetShort() - { - var ret = Marshal.ReadInt16(_ptr); - _ptr += sizeof(short); + /// + /// Reads a 16-bit integer from the current pointer and advances the pointer by the size of a short. + /// + /// The 16-bit integer read from the pointer. + public short GetShort() + { + var ret = Marshal.ReadInt16(_ptr); + _ptr += sizeof(short); - return ret; - } + return ret; + } - /// - /// Reads an from the current pointer and advances the pointer by the size of an . - /// - /// The read from the pointer. - public IntPtr GetIntPtr() - { - var ret = Marshal.ReadIntPtr(_ptr); - _ptr += IntPtr.Size; + /// + /// Reads an from the current pointer and advances the pointer by the size of an . + /// + /// The read from the pointer. + public IntPtr GetIntPtr() + { + var ret = Marshal.ReadIntPtr(_ptr); + _ptr += IntPtr.Size; - return ret; - } + return ret; + } - /// - /// Reads a null-terminated ANSI string from the current pointer and advances the pointer by the size of an . - /// - /// The ANSI string read from the pointer. If the string is null, an empty string is returned. - public string GetString() - { - var str = _ptr.ToAnsi(); - _ptr += IntPtr.Size; + /// + /// Reads a null-terminated ANSI string from the current pointer and advances the pointer by the size of an . + /// + /// The ANSI string read from the pointer. If the string is null, an empty string is returned. + public string GetString() + { + var str = _ptr.ToAnsi(); + _ptr += IntPtr.Size; - return str is null ? string.Empty : str.Trim(); - } + return str is null ? string.Empty : str.Trim(); + } - /// - /// Reads an ANSI string of the specified length from the current pointer and advances the pointer by the specified length. - /// - /// The length of the string to read. - /// The ANSI string read from the pointer. If the string is null, an empty string is returned. - public string GetString(int length) - { - var str = _ptr.ToAnsi(length); + /// + /// Reads an ANSI string of the specified length from the current pointer and advances the pointer by the specified length. + /// + /// The length of the string to read. + /// The ANSI string read from the pointer. If the string is null, an empty string is returned. + public string GetString(int length) + { + var str = _ptr.ToAnsi(length); - _ptr += length; + _ptr += length; - return str is null ? string.Empty : str.Trim(); - } + return str is null ? string.Empty : str.Trim(); } } \ No newline at end of file diff --git a/Interop/SafePointer.cs b/Interop/SafePointer.cs index aa5a21a3..d56dc9f6 100644 --- a/Interop/SafePointer.cs +++ b/Interop/SafePointer.cs @@ -1,166 +1,165 @@ -namespace Ecng.Interop +namespace Ecng.Interop; + +using System; + +using Ecng.Common; + +/// +/// Represents a safe wrapper around an unmanaged memory pointer with bounds checking and shifting capabilities. +/// +public struct SafePointer { - using System; + private IntPtr _pointer; + private readonly int? _size; + private int _origin; + + /// + /// Initializes a new instance of the struct with a specified pointer and optional size. + /// + /// The unmanaged memory pointer to wrap. Must not be . + /// The optional size of the memory block in bytes. If specified, must be non-negative. + /// + /// Thrown when is or is negative. + /// + public SafePointer(IntPtr pointer, int? size) + { + if (pointer == default) + throw new ArgumentOutOfRangeException(nameof(pointer)); + + if (size < 0) + throw new ArgumentOutOfRangeException(nameof(size)); + + _pointer = pointer; + _size = size; + _origin = 0; + } + + /// + /// Gets the current unmanaged memory pointer. + /// + public IntPtr Pointer => _pointer; + + private void CheckBorder() + where TStruct : struct + { + CheckBorder(typeof(TStruct).SizeOf()); + } + + private void CheckBorder(int offset) + { + if (_size != null && (_origin + offset) > _size.Value) + throw new ArgumentOutOfRangeException(nameof(offset)); + } + + /// + /// Reads a structure of type from the current pointer position. + /// + /// The type of the structure to read. Must be a value type. + /// If true, shifts the pointer by the size of after reading. + /// The structure read from the pointer. + /// + /// Thrown when the operation exceeds the defined size boundary. + /// + public TStruct ToStruct(bool autoShift = false) + where TStruct : struct + { + CheckBorder(); + + var value = _pointer.ToStruct(); + + if (autoShift) + Shift(); + + return value; + } + + /// + /// Shifts the pointer forward by the size of the specified structure type. + /// + /// The type of the structure whose size determines the shift. Must be a value type. + /// + /// Thrown when the shift exceeds the defined size boundary. + /// + public void Shift() + where TStruct : struct + { + Shift(typeof(TStruct).SizeOf()); + } + + /// + /// Shifts the pointer forward by the specified offset in bytes. + /// + /// The number of bytes to shift the pointer. + /// + /// Thrown when the shift exceeds the defined size boundary. + /// + public void Shift(int offset) + { + CheckBorder(offset); - using Ecng.Common; + _origin += offset; + _pointer += offset; + } /// - /// Represents a safe wrapper around an unmanaged memory pointer with bounds checking and shifting capabilities. + /// Copies data from the current pointer position to a byte array. /// - public struct SafePointer + /// The target byte array to copy data into. + /// If true, shifts the pointer by the length of the copied data after copying. + /// + /// Thrown when the operation exceeds the defined size boundary. + /// + public void CopyTo(byte[] buffer, bool autoShift = false) { - private IntPtr _pointer; - private readonly int? _size; - private int _origin; - - /// - /// Initializes a new instance of the struct with a specified pointer and optional size. - /// - /// The unmanaged memory pointer to wrap. Must not be . - /// The optional size of the memory block in bytes. If specified, must be non-negative. - /// - /// Thrown when is or is negative. - /// - public SafePointer(IntPtr pointer, int? size) - { - if (pointer == default) - throw new ArgumentOutOfRangeException(nameof(pointer)); - - if (size < 0) - throw new ArgumentOutOfRangeException(nameof(size)); - - _pointer = pointer; - _size = size; - _origin = 0; - } - - /// - /// Gets the current unmanaged memory pointer. - /// - public IntPtr Pointer => _pointer; - - private void CheckBorder() - where TStruct : struct - { - CheckBorder(typeof(TStruct).SizeOf()); - } - - private void CheckBorder(int offset) - { - if (_size != null && (_origin + offset) > _size.Value) - throw new ArgumentOutOfRangeException(nameof(offset)); - } - - /// - /// Reads a structure of type from the current pointer position. - /// - /// The type of the structure to read. Must be a value type. - /// If true, shifts the pointer by the size of after reading. - /// The structure read from the pointer. - /// - /// Thrown when the operation exceeds the defined size boundary. - /// - public TStruct ToStruct(bool autoShift = false) - where TStruct : struct - { - CheckBorder(); - - var value = _pointer.ToStruct(); - - if (autoShift) - Shift(); - - return value; - } - - /// - /// Shifts the pointer forward by the size of the specified structure type. - /// - /// The type of the structure whose size determines the shift. Must be a value type. - /// - /// Thrown when the shift exceeds the defined size boundary. - /// - public void Shift() - where TStruct : struct - { - Shift(typeof(TStruct).SizeOf()); - } - - /// - /// Shifts the pointer forward by the specified offset in bytes. - /// - /// The number of bytes to shift the pointer. - /// - /// Thrown when the shift exceeds the defined size boundary. - /// - public void Shift(int offset) - { - CheckBorder(offset); - - _origin += offset; - _pointer += offset; - } - - /// - /// Copies data from the current pointer position to a byte array. - /// - /// The target byte array to copy data into. - /// If true, shifts the pointer by the length of the copied data after copying. - /// - /// Thrown when the operation exceeds the defined size boundary. - /// - public void CopyTo(byte[] buffer, bool autoShift = false) - { - CopyTo(buffer, 0, buffer.Length, autoShift); - } - - /// - /// Copies a specified amount of data from the current pointer position to a byte array. - /// - /// The target byte array to copy data into. - /// The starting index in the buffer where data should be copied. - /// The number of bytes to copy. - /// If true, shifts the pointer by after copying. - /// - /// Thrown when the operation exceeds the defined size boundary. - /// - public void CopyTo(byte[] buffer, int offset, int length, bool autoShift = false) - { - CheckBorder(length); - - Pointer.CopyTo(buffer, offset, length); - - if (autoShift) - Shift(length); - } - - /// - /// Reads a value of type from the current pointer position. - /// - /// The type of the value to read. Must be a value type. - /// If true, shifts the pointer by the size of after reading. - /// The value read from the pointer. - /// - /// Thrown when the operation exceeds the defined size boundary. - /// - public TValue Read(bool autoShift = false) - where TValue : struct - { - CheckBorder(); - - var value = Pointer.Read(); - - if (autoShift) - Shift(); - - return value; - } - - /// - /// Implicitly converts a to an . - /// - /// The instance to convert. - /// The underlying value. - public static implicit operator IntPtr(SafePointer pointer) => pointer.Pointer; + CopyTo(buffer, 0, buffer.Length, autoShift); } + + /// + /// Copies a specified amount of data from the current pointer position to a byte array. + /// + /// The target byte array to copy data into. + /// The starting index in the buffer where data should be copied. + /// The number of bytes to copy. + /// If true, shifts the pointer by after copying. + /// + /// Thrown when the operation exceeds the defined size boundary. + /// + public void CopyTo(byte[] buffer, int offset, int length, bool autoShift = false) + { + CheckBorder(length); + + Pointer.CopyTo(buffer, offset, length); + + if (autoShift) + Shift(length); + } + + /// + /// Reads a value of type from the current pointer position. + /// + /// The type of the value to read. Must be a value type. + /// If true, shifts the pointer by the size of after reading. + /// The value read from the pointer. + /// + /// Thrown when the operation exceeds the defined size boundary. + /// + public TValue Read(bool autoShift = false) + where TValue : struct + { + CheckBorder(); + + var value = Pointer.Read(); + + if (autoShift) + Shift(); + + return value; + } + + /// + /// Implicitly converts a to an . + /// + /// The instance to convert. + /// The underlying value. + public static implicit operator IntPtr(SafePointer pointer) => pointer.Pointer; } \ No newline at end of file diff --git a/Linq/ExpressionExtensions.cs b/Linq/ExpressionExtensions.cs index a1597b4e..94d3b5e3 100644 --- a/Linq/ExpressionExtensions.cs +++ b/Linq/ExpressionExtensions.cs @@ -1,145 +1,144 @@ -namespace Ecng.Linq -{ - using System; - using System.Linq; - using System.Linq.Expressions; - using System.Reflection; +namespace Ecng.Linq; + +using System; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; + +using Ecng.Common; - using Ecng.Common; +/// +/// The extensions for . +/// +public static class ExpressionExtensions +{ + /// + /// Gets the constant value from the expression. + /// + /// The type of the constant value. + /// The expression. + /// The constant value. + public static T GetConstant(this Expression e) + => ((ConstantExpression)e).Value.To(); /// - /// The extensions for . + /// Evaluates the expression and returns its value. /// - public static class ExpressionExtensions + /// The expression to evaluate. + /// The evaluated value. + public static object Evaluate(this Expression e) { - /// - /// Gets the constant value from the expression. - /// - /// The type of the constant value. - /// The expression. - /// The constant value. - public static T GetConstant(this Expression e) - => ((ConstantExpression)e).Value.To(); - - /// - /// Evaluates the expression and returns its value. - /// - /// The expression to evaluate. - /// The evaluated value. - public static object Evaluate(this Expression e) - { - //A little optimization for constant expressions - if (e.NodeType == ExpressionType.Constant) - return e.GetConstant(); + //A little optimization for constant expressions + if (e.NodeType == ExpressionType.Constant) + return e.GetConstant(); - return Expression.Lambda(e).Compile().DynamicInvoke(); - } + return Expression.Lambda(e).Compile().DynamicInvoke(); + } - /// - /// Replaces the source provider in the expression with the specified query provider. - /// - /// The expression to modify. - /// The query provider to set. - /// Thrown when expression or provider is null. - public static void ReplaceSource(this Expression expression, IQueryProvider provider) - { - if (expression is null) - throw new ArgumentNullException(nameof(expression)); + /// + /// Replaces the source provider in the expression with the specified query provider. + /// + /// The expression to modify. + /// The query provider to set. + /// Thrown when expression or provider is null. + public static void ReplaceSource(this Expression expression, IQueryProvider provider) + { + if (expression is null) + throw new ArgumentNullException(nameof(expression)); - if (provider is null) - throw new ArgumentNullException(nameof(provider)); + if (provider is null) + throw new ArgumentNullException(nameof(provider)); - while (expression is MethodCallExpression mce) - expression = mce.Arguments[0]; + while (expression is MethodCallExpression mce) + expression = mce.Arguments[0]; - var field = expression.GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic).First(); - field.SetValue(expression, provider); - } + var field = expression.GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic).First(); + field.SetValue(expression, provider); + } - /// - /// Removes quote expressions from the given expression. - /// - /// The expression to process. - /// The unquoted expression. - public static Expression StripQuotes(this Expression e) + /// + /// Removes quote expressions from the given expression. + /// + /// The expression to process. + /// The unquoted expression. + public static Expression StripQuotes(this Expression e) + { + while (e.NodeType == ExpressionType.Quote) { - while (e.NodeType == ExpressionType.Quote) - { - e = ((UnaryExpression)e).Operand; - } - - return e; + e = ((UnaryExpression)e).Operand; } - /// - /// Gets the value of the specified member from the given instance. - /// - /// The member whose value to retrieve. - /// The object instance from which to retrieve the value. - /// The value of the member. - /// Thrown when the member type is not supported. - public static object GetMemberValue(this MemberInfo member, object instance) - { - if (member is PropertyInfo pi) - return pi.GetValue(instance); - else if (member is FieldInfo fi) - return fi.GetValue(instance); - else - throw new NotSupportedException(); - } + return e; + } - /// - /// Retrieves the innermost member in a nested member expression. - /// - /// The member expression to process. - /// The innermost member expression. - public static MemberExpression GetInnerMember(this MemberExpression exp) - { - if (exp.Expression is MemberExpression d) - return GetInnerMember(d); + /// + /// Gets the value of the specified member from the given instance. + /// + /// The member whose value to retrieve. + /// The object instance from which to retrieve the value. + /// The value of the member. + /// Thrown when the member type is not supported. + public static object GetMemberValue(this MemberInfo member, object instance) + { + if (member is PropertyInfo pi) + return pi.GetValue(instance); + else if (member is FieldInfo fi) + return fi.GetValue(instance); + else + throw new NotSupportedException(); + } - return exp; - } + /// + /// Retrieves the innermost member in a nested member expression. + /// + /// The member expression to process. + /// The innermost member expression. + public static MemberExpression GetInnerMember(this MemberExpression exp) + { + if (exp.Expression is MemberExpression d) + return GetInnerMember(d); - /// - /// Evaluates the expression and returns its value as the specified type. - /// - /// The type to convert the evaluated value to. - /// The expression to evaluate. - /// The evaluated value converted to the specified type. - /// Thrown when the expression is null. - /// Thrown when the expression type is not supported. - public static TValue GetValue(this Expression exp) - { - if (exp is null) - throw new ArgumentNullException(nameof(exp)); + return exp; + } - if (exp is ConstantExpression c) - return c.Value.To(); - else if (exp is MemberExpression me) - return me.Member.GetMemberValue(me.Expression is null ? null : GetValue(me.Expression)).To(); + /// + /// Evaluates the expression and returns its value as the specified type. + /// + /// The type to convert the evaluated value to. + /// The expression to evaluate. + /// The evaluated value converted to the specified type. + /// Thrown when the expression is null. + /// Thrown when the expression type is not supported. + public static TValue GetValue(this Expression exp) + { + if (exp is null) + throw new ArgumentNullException(nameof(exp)); - throw new ArgumentOutOfRangeException(exp.NodeType.ToString()); - } + if (exp is ConstantExpression c) + return c.Value.To(); + else if (exp is MemberExpression me) + return me.Member.GetMemberValue(me.Expression is null ? null : GetValue(me.Expression)).To(); + + throw new ArgumentOutOfRangeException(exp.NodeType.ToString()); + } - /// - /// Converts the given expression type to its equivalent . - /// - /// The expression type. - /// The corresponding . - /// Thrown when the expression type has no corresponding operator. - public static ComparisonOperator ToOperator(this ExpressionType type) + /// + /// Converts the given expression type to its equivalent . + /// + /// The expression type. + /// The corresponding . + /// Thrown when the expression type has no corresponding operator. + public static ComparisonOperator ToOperator(this ExpressionType type) + { + return type switch { - return type switch - { - ExpressionType.GreaterThan => ComparisonOperator.Greater, - ExpressionType.GreaterThanOrEqual => ComparisonOperator.GreaterOrEqual, - ExpressionType.LessThan => ComparisonOperator.Less, - ExpressionType.LessThanOrEqual => ComparisonOperator.LessOrEqual, - ExpressionType.Equal => ComparisonOperator.Equal, - ExpressionType.NotEqual => ComparisonOperator.NotEqual, - _ => throw new ArgumentOutOfRangeException(type.To()), - }; - } + ExpressionType.GreaterThan => ComparisonOperator.Greater, + ExpressionType.GreaterThanOrEqual => ComparisonOperator.GreaterOrEqual, + ExpressionType.LessThan => ComparisonOperator.Less, + ExpressionType.LessThanOrEqual => ComparisonOperator.LessOrEqual, + ExpressionType.Equal => ComparisonOperator.Equal, + ExpressionType.NotEqual => ComparisonOperator.NotEqual, + _ => throw new ArgumentOutOfRangeException(type.To()), + }; } } \ No newline at end of file diff --git a/Linq/QueryableExtensions.cs b/Linq/QueryableExtensions.cs index 88f11013..511a784d 100644 --- a/Linq/QueryableExtensions.cs +++ b/Linq/QueryableExtensions.cs @@ -1,360 +1,359 @@ -namespace Ecng.Linq +namespace Ecng.Linq; + +using System; +using System.Linq; +using System.Linq.Expressions; +using System.Threading; +using System.Threading.Tasks; +using System.Reflection; + +using Ecng.Common; + +/// +/// Provides extension methods for to support asynchronous operations and dynamic ordering. +/// +/// +/// This class includes methods to perform operations such as SkipLong, AnyAsync, FirstOrDefaultAsync, CountAsync, and ToArrayAsync. +/// Furthermore, it provides dynamic ordering methods to order the results by property names. +/// +public static class QueryableExtensions { - using System; - using System.Linq; - using System.Linq.Expressions; - using System.Threading; - using System.Threading.Tasks; - using System.Reflection; + /// + /// Skips the specified number of elements in the source sequence. + /// + /// The type of the elements. + /// The source queryable sequence. + /// The number of elements to skip. + /// An that contains the elements that occur after the specified number of elements. + public static IQueryable SkipLong(this IQueryable source, long count) + { + if (source is null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.CreateQuery( + Expression.Call( + null, + GetMethodInfo(SkipLong, source, count), + [source.Expression, Expression.Constant(count)] + )); + } + + /// + /// Asynchronously determines whether a sequence contains any elements. + /// + /// The type of the elements. + /// The source queryable sequence. + /// A token to monitor for cancellation requests. + /// A representing the asynchronous operation. The task result contains true if the sequence contains any elements; otherwise, false. + public static async ValueTask AnyAsync(this IQueryable source, CancellationToken cancellationToken) + => await source.FirstOrDefaultAsync(cancellationToken) is not null; + + /// + /// Asynchronously returns the first element of a sequence, or a default value if the sequence contains no elements. + /// + /// The type of the elements. + /// The source queryable sequence. + /// A token to monitor for cancellation requests. + /// A representing the asynchronous operation. The task result contains the first element in the sequence or a default value if no element is found. + public static ValueTask FirstOrDefaultAsync(this IQueryable source, CancellationToken cancellationToken) + => source.Provider.Execute>( + Expression.Call( + null, + GetMethodInfo(FirstOrDefaultAsync, source, cancellationToken), + [ + source.Expression, + Expression.Constant(cancellationToken), + ] + ) + ); - using Ecng.Common; + /// + /// Dynamically orders the elements of a sequence in ascending order based on a property name. + /// + /// The type of the elements. + /// The source queryable sequence. + /// The property name to order by. + /// If set to true, ignores case during property name comparison. + /// An whose elements are sorted according to the specified property. + public static IOrderedQueryable OrderBy(this IQueryable source, string propertyName, bool ignoreCase) + => ApplyOrder(source, propertyName, ignoreCase, nameof(OrderBy)); /// - /// Provides extension methods for to support asynchronous operations and dynamic ordering. + /// Dynamically orders the elements of a sequence in descending order based on a property name. /// - /// - /// This class includes methods to perform operations such as SkipLong, AnyAsync, FirstOrDefaultAsync, CountAsync, and ToArrayAsync. - /// Furthermore, it provides dynamic ordering methods to order the results by property names. - /// - public static class QueryableExtensions + /// The type of the elements. + /// The source queryable sequence. + /// The property name to order by. + /// If set to true, ignores case during property name comparison. + /// An whose elements are sorted in descending order according to the specified property. + public static IOrderedQueryable OrderByDescending(this IQueryable source, string propertyName, bool ignoreCase) + => ApplyOrder(source, propertyName, ignoreCase, nameof(OrderByDescending)); + + /// + /// Dynamically performs a subsequent ordering of the elements of a sequence in ascending order based on a property name. + /// + /// The type of the elements. + /// An ordered queryable sequence. + /// The property name to order by. + /// If set to true, ignores case during property name comparison. + /// An whose elements are further sorted according to the specified property. + public static IOrderedQueryable ThenBy(this IOrderedQueryable source, string propertyName, bool ignoreCase) + => ApplyOrder(source, propertyName, ignoreCase, nameof(ThenBy)); + + /// + /// Dynamically performs a subsequent ordering of the elements of a sequence in descending order based on a property name. + /// + /// The type of the elements. + /// An ordered queryable sequence. + /// The property name to order by. + /// If set to true, ignores case during property name comparison. + /// An whose elements are further sorted in descending order according to the specified property. + public static IOrderedQueryable ThenByDescending(this IOrderedQueryable source, string propertyName, bool ignoreCase) + => ApplyOrder(source, propertyName, ignoreCase, nameof(ThenByDescending)); + + // https://stackoverflow.com/a/233505 + private static IOrderedQueryable ApplyOrder(this IQueryable source, string propertyName, bool ignoreCase, string methodName) { - /// - /// Skips the specified number of elements in the source sequence. - /// - /// The type of the elements. - /// The source queryable sequence. - /// The number of elements to skip. - /// An that contains the elements that occur after the specified number of elements. - public static IQueryable SkipLong(this IQueryable source, long count) + var type = typeof(T); + var arg = Expression.Parameter(type, "x"); + + var flags = BindingFlags.Public | BindingFlags.Instance; + + if (ignoreCase) + flags |= BindingFlags.IgnoreCase; + + Expression expr = arg; + + foreach (var prop in propertyName.Split('.')) { - if (source is null) - throw new ArgumentNullException(nameof(source)); + var pi = type.GetProperty(prop, flags); - return source.Provider.CreateQuery( - Expression.Call( - null, - GetMethodInfo(SkipLong, source, count), - [source.Expression, Expression.Constant(count)] - )); + if (pi is null) + throw new InvalidOperationException($"Type '{type}' doesn't contains {prop} property."); + + expr = Expression.Property(expr, pi); + type = pi.PropertyType; } - /// - /// Asynchronously determines whether a sequence contains any elements. - /// - /// The type of the elements. - /// The source queryable sequence. - /// A token to monitor for cancellation requests. - /// A representing the asynchronous operation. The task result contains true if the sequence contains any elements; otherwise, false. - public static async ValueTask AnyAsync(this IQueryable source, CancellationToken cancellationToken) - => await source.FirstOrDefaultAsync(cancellationToken) is not null; - - /// - /// Asynchronously returns the first element of a sequence, or a default value if the sequence contains no elements. - /// - /// The type of the elements. - /// The source queryable sequence. - /// A token to monitor for cancellation requests. - /// A representing the asynchronous operation. The task result contains the first element in the sequence or a default value if no element is found. - public static ValueTask FirstOrDefaultAsync(this IQueryable source, CancellationToken cancellationToken) - => source.Provider.Execute>( - Expression.Call( - null, - GetMethodInfo(FirstOrDefaultAsync, source, cancellationToken), - [ - source.Expression, - Expression.Constant(cancellationToken), - ] - ) - ); - - /// - /// Dynamically orders the elements of a sequence in ascending order based on a property name. - /// - /// The type of the elements. - /// The source queryable sequence. - /// The property name to order by. - /// If set to true, ignores case during property name comparison. - /// An whose elements are sorted according to the specified property. - public static IOrderedQueryable OrderBy(this IQueryable source, string propertyName, bool ignoreCase) - => ApplyOrder(source, propertyName, ignoreCase, nameof(OrderBy)); - - /// - /// Dynamically orders the elements of a sequence in descending order based on a property name. - /// - /// The type of the elements. - /// The source queryable sequence. - /// The property name to order by. - /// If set to true, ignores case during property name comparison. - /// An whose elements are sorted in descending order according to the specified property. - public static IOrderedQueryable OrderByDescending(this IQueryable source, string propertyName, bool ignoreCase) - => ApplyOrder(source, propertyName, ignoreCase, nameof(OrderByDescending)); - - /// - /// Dynamically performs a subsequent ordering of the elements of a sequence in ascending order based on a property name. - /// - /// The type of the elements. - /// An ordered queryable sequence. - /// The property name to order by. - /// If set to true, ignores case during property name comparison. - /// An whose elements are further sorted according to the specified property. - public static IOrderedQueryable ThenBy(this IOrderedQueryable source, string propertyName, bool ignoreCase) - => ApplyOrder(source, propertyName, ignoreCase, nameof(ThenBy)); - - /// - /// Dynamically performs a subsequent ordering of the elements of a sequence in descending order based on a property name. - /// - /// The type of the elements. - /// An ordered queryable sequence. - /// The property name to order by. - /// If set to true, ignores case during property name comparison. - /// An whose elements are further sorted in descending order according to the specified property. - public static IOrderedQueryable ThenByDescending(this IOrderedQueryable source, string propertyName, bool ignoreCase) - => ApplyOrder(source, propertyName, ignoreCase, nameof(ThenByDescending)); - - // https://stackoverflow.com/a/233505 - private static IOrderedQueryable ApplyOrder(this IQueryable source, string propertyName, bool ignoreCase, string methodName) - { - var type = typeof(T); - var arg = Expression.Parameter(type, "x"); + var delegateType = typeof(Func<,>).Make(typeof(T), type); + var lambda = Expression.Lambda(delegateType, expr, arg); + + object result = typeof(Queryable).GetMethods().Single( + method => method.Name == methodName + && method.IsGenericMethodDefinition + && method.GetGenericArguments().Length == 2 + && method.GetParameters().Length == 2) + .MakeGenericMethod(typeof(T), type) + .Invoke(null, [source, lambda]); + + return (IOrderedQueryable)result; + } + + /// + /// Asynchronously counts the number of elements in a sequence. + /// + /// The type of the elements. + /// The source queryable sequence. + /// A token to monitor for cancellation requests. + /// A representing the asynchronous operation. The task result contains the count of elements. + public static ValueTask CountAsync(this IQueryable source, CancellationToken cancellationToken) + { + if (source is null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.Execute>( + Expression.Call( + null, + GetMethodInfo(CountAsync, source, cancellationToken), + [source.Expression, Expression.Constant(cancellationToken)] + )); + } - var flags = BindingFlags.Public | BindingFlags.Instance; + /// + /// Asynchronously creates an array from a sequence. + /// + /// The type of the elements. + /// The source queryable sequence. + /// A token to monitor for cancellation requests. + /// A representing the asynchronous operation. The task result contains an array that holds the elements from the sequence. + public static ValueTask ToArrayAsync(this IQueryable source, CancellationToken cancellationToken) + { + if (source is null) + throw new ArgumentNullException(nameof(source)); + + return source.Provider.Execute>(Expression.Call(null, GetMethodInfo(ToArrayAsync, source, cancellationToken), + [ + source.Expression, + Expression.Constant(cancellationToken) + ])); + } - if (ignoreCase) - flags |= BindingFlags.IgnoreCase; + #region Helper methods to obtain MethodInfo in a safe way - Expression expr = arg; + /// + /// Retrieves the for the specified delegate. + /// + /// The type of the delegate parameter. + /// The return type of the delegate. + /// The delegate to obtain the method information from. + /// A parameter used only for type inference. + /// The of the delegate. + public static MethodInfo GetMethodInfo(Func f, T1 unused1) + => f.Method; - foreach (var prop in propertyName.Split('.')) - { - var pi = type.GetProperty(prop, flags); + /// + /// Retrieves the for the specified delegate. + /// + /// The type of the first delegate parameter. + /// The type of the second delegate parameter. + /// The return type of the delegate. + /// The delegate to obtain the method information from. + /// A parameter used only for type inference. + /// A parameter used only for type inference. + /// The of the delegate. + public static MethodInfo GetMethodInfo(Func f, T1 unused1, T2 unused2) + => f.Method; - if (pi is null) - throw new InvalidOperationException($"Type '{type}' doesn't contains {prop} property."); + /// + /// Retrieves the for the specified delegate. + /// + /// The type of the first delegate parameter. + /// The type of the second delegate parameter. + /// The type of the third delegate parameter. + /// The return type of the delegate. + /// The delegate to obtain the method information from. + /// A parameter used only for type inference. + /// A parameter used only for type inference. + /// A parameter used only for type inference. + /// The of the delegate. + public static MethodInfo GetMethodInfo(Func f, T1 unused1, T2 unused2, T3 unused3) + => f.Method; - expr = Expression.Property(expr, pi); - type = pi.PropertyType; - } + /// + /// Retrieves the for the specified delegate. + /// + /// The type of the first delegate parameter. + /// The type of the second delegate parameter. + /// The type of the third delegate parameter. + /// The type of the fourth delegate parameter. + /// The return type of the delegate. + /// The delegate to obtain the method information from. + /// A parameter used only for type inference. + /// A parameter used only for type inference. + /// A parameter used only for type inference. + /// A parameter used only for type inference. + /// The of the delegate. + public static MethodInfo GetMethodInfo(Func f, T1 unused1, T2 unused2, T3 unused3, T4 unused4) + => f.Method; - var delegateType = typeof(Func<,>).Make(typeof(T), type); - var lambda = Expression.Lambda(delegateType, expr, arg); + /// + /// Retrieves the for the specified delegate. + /// + /// The type of the first delegate parameter. + /// The type of the second delegate parameter. + /// The type of the third delegate parameter. + /// The type of the fourth delegate parameter. + /// The type of the fifth delegate parameter. + /// The return type of the delegate. + /// The delegate to obtain the method information from. + /// A parameter used only for type inference. + /// A parameter used only for type inference. + /// A parameter used only for type inference. + /// A parameter used only for type inference. + /// A parameter used only for type inference. + /// The of the delegate. + public static MethodInfo GetMethodInfo(Func f, T1 unused1, T2 unused2, T3 unused3, T4 unused4, T5 unused5) + => f.Method; - object result = typeof(Queryable).GetMethods().Single( - method => method.Name == methodName - && method.IsGenericMethodDefinition - && method.GetGenericArguments().Length == 2 - && method.GetParameters().Length == 2) - .MakeGenericMethod(typeof(T), type) - .Invoke(null, [source, lambda]); + /// + /// Retrieves the for the specified delegate. + /// + /// The type of the delegate parameter. + /// The delegate to obtain the method information from. + /// The of the delegate. + public static MethodInfo GetMethodInfo(Action f) + => f.Method; - return (IOrderedQueryable)result; - } + /// + /// Retrieves the for the specified delegate. + /// + /// The type of the first delegate parameter. + /// The type of the second delegate parameter. + /// The delegate to obtain the method information from. + /// A parameter used only for type inference. + /// A parameter used only for type inference. + /// The of the delegate. + public static MethodInfo GetMethodInfo(Action f, T1 unused1, T2 unused2) + => f.Method; - /// - /// Asynchronously counts the number of elements in a sequence. - /// - /// The type of the elements. - /// The source queryable sequence. - /// A token to monitor for cancellation requests. - /// A representing the asynchronous operation. The task result contains the count of elements. - public static ValueTask CountAsync(this IQueryable source, CancellationToken cancellationToken) - { - if (source is null) - throw new ArgumentNullException(nameof(source)); + /// + /// Retrieves the for the specified delegate. + /// + /// The type of the first delegate parameter. + /// The type of the second delegate parameter. + /// The type of the third delegate parameter. + /// The delegate to obtain the method information from. + /// A parameter used only for type inference. + /// A parameter used only for type inference. + /// A parameter used only for type inference. + /// The of the delegate. + public static MethodInfo GetMethodInfo(Action f, T1 unused1, T2 unused2, T3 unused3) + => f.Method; - return source.Provider.Execute>( - Expression.Call( - null, - GetMethodInfo(CountAsync, source, cancellationToken), - [source.Expression, Expression.Constant(cancellationToken)] - )); - } + /// + /// Retrieves the for the specified delegate. + /// + /// The type of the first delegate parameter. + /// The type of the second delegate parameter. + /// The type of the third delegate parameter. + /// The type of the fourth delegate parameter. + /// The delegate to obtain the method information from. + /// A parameter used only for type inference. + /// A parameter used only for type inference. + /// A parameter used only for type inference. + /// A parameter used only for type inference. + /// The of the delegate. + public static MethodInfo GetMethodInfo(Action f, T1 unused1, T2 unused2, T3 unused3, T4 unused4) + => f.Method; - /// - /// Asynchronously creates an array from a sequence. - /// - /// The type of the elements. - /// The source queryable sequence. - /// A token to monitor for cancellation requests. - /// A representing the asynchronous operation. The task result contains an array that holds the elements from the sequence. - public static ValueTask ToArrayAsync(this IQueryable source, CancellationToken cancellationToken) - { - if (source is null) - throw new ArgumentNullException(nameof(source)); - - return source.Provider.Execute>(Expression.Call(null, GetMethodInfo(ToArrayAsync, source, cancellationToken), - [ - source.Expression, - Expression.Constant(cancellationToken) - ])); - } + /// + /// Retrieves the for the specified delegate. + /// + /// The type of the first delegate parameter. + /// The type of the second delegate parameter. + /// The type of the third delegate parameter. + /// The type of the fourth delegate parameter. + /// The type of the fifth delegate parameter. + /// The delegate to obtain the method information from. + /// A parameter used only for type inference. + /// A parameter used only for type inference. + /// A parameter used only for type inference. + /// A parameter used only for type inference. + /// A parameter used only for type inference. + /// The of the delegate. + public static MethodInfo GetMethodInfo(Action f, T1 unused1, T2 unused2, T3 unused3, T4 unused4, T5 unused5) + => f.Method; - #region Helper methods to obtain MethodInfo in a safe way - - /// - /// Retrieves the for the specified delegate. - /// - /// The type of the delegate parameter. - /// The return type of the delegate. - /// The delegate to obtain the method information from. - /// A parameter used only for type inference. - /// The of the delegate. - public static MethodInfo GetMethodInfo(Func f, T1 unused1) - => f.Method; - - /// - /// Retrieves the for the specified delegate. - /// - /// The type of the first delegate parameter. - /// The type of the second delegate parameter. - /// The return type of the delegate. - /// The delegate to obtain the method information from. - /// A parameter used only for type inference. - /// A parameter used only for type inference. - /// The of the delegate. - public static MethodInfo GetMethodInfo(Func f, T1 unused1, T2 unused2) - => f.Method; - - /// - /// Retrieves the for the specified delegate. - /// - /// The type of the first delegate parameter. - /// The type of the second delegate parameter. - /// The type of the third delegate parameter. - /// The return type of the delegate. - /// The delegate to obtain the method information from. - /// A parameter used only for type inference. - /// A parameter used only for type inference. - /// A parameter used only for type inference. - /// The of the delegate. - public static MethodInfo GetMethodInfo(Func f, T1 unused1, T2 unused2, T3 unused3) - => f.Method; - - /// - /// Retrieves the for the specified delegate. - /// - /// The type of the first delegate parameter. - /// The type of the second delegate parameter. - /// The type of the third delegate parameter. - /// The type of the fourth delegate parameter. - /// The return type of the delegate. - /// The delegate to obtain the method information from. - /// A parameter used only for type inference. - /// A parameter used only for type inference. - /// A parameter used only for type inference. - /// A parameter used only for type inference. - /// The of the delegate. - public static MethodInfo GetMethodInfo(Func f, T1 unused1, T2 unused2, T3 unused3, T4 unused4) - => f.Method; - - /// - /// Retrieves the for the specified delegate. - /// - /// The type of the first delegate parameter. - /// The type of the second delegate parameter. - /// The type of the third delegate parameter. - /// The type of the fourth delegate parameter. - /// The type of the fifth delegate parameter. - /// The return type of the delegate. - /// The delegate to obtain the method information from. - /// A parameter used only for type inference. - /// A parameter used only for type inference. - /// A parameter used only for type inference. - /// A parameter used only for type inference. - /// A parameter used only for type inference. - /// The of the delegate. - public static MethodInfo GetMethodInfo(Func f, T1 unused1, T2 unused2, T3 unused3, T4 unused4, T5 unused5) - => f.Method; - - /// - /// Retrieves the for the specified delegate. - /// - /// The type of the delegate parameter. - /// The delegate to obtain the method information from. - /// The of the delegate. - public static MethodInfo GetMethodInfo(Action f) - => f.Method; - - /// - /// Retrieves the for the specified delegate. - /// - /// The type of the first delegate parameter. - /// The type of the second delegate parameter. - /// The delegate to obtain the method information from. - /// A parameter used only for type inference. - /// A parameter used only for type inference. - /// The of the delegate. - public static MethodInfo GetMethodInfo(Action f, T1 unused1, T2 unused2) - => f.Method; - - /// - /// Retrieves the for the specified delegate. - /// - /// The type of the first delegate parameter. - /// The type of the second delegate parameter. - /// The type of the third delegate parameter. - /// The delegate to obtain the method information from. - /// A parameter used only for type inference. - /// A parameter used only for type inference. - /// A parameter used only for type inference. - /// The of the delegate. - public static MethodInfo GetMethodInfo(Action f, T1 unused1, T2 unused2, T3 unused3) - => f.Method; - - /// - /// Retrieves the for the specified delegate. - /// - /// The type of the first delegate parameter. - /// The type of the second delegate parameter. - /// The type of the third delegate parameter. - /// The type of the fourth delegate parameter. - /// The delegate to obtain the method information from. - /// A parameter used only for type inference. - /// A parameter used only for type inference. - /// A parameter used only for type inference. - /// A parameter used only for type inference. - /// The of the delegate. - public static MethodInfo GetMethodInfo(Action f, T1 unused1, T2 unused2, T3 unused3, T4 unused4) - => f.Method; - - /// - /// Retrieves the for the specified delegate. - /// - /// The type of the first delegate parameter. - /// The type of the second delegate parameter. - /// The type of the third delegate parameter. - /// The type of the fourth delegate parameter. - /// The type of the fifth delegate parameter. - /// The delegate to obtain the method information from. - /// A parameter used only for type inference. - /// A parameter used only for type inference. - /// A parameter used only for type inference. - /// A parameter used only for type inference. - /// A parameter used only for type inference. - /// The of the delegate. - public static MethodInfo GetMethodInfo(Action f, T1 unused1, T2 unused2, T3 unused3, T4 unused4, T5 unused5) - => f.Method; - - /// - /// Retrieves the for the specified delegate. - /// - /// The type of the first delegate parameter. - /// The type of the second delegate parameter. - /// The type of the third delegate parameter. - /// The type of the fourth delegate parameter. - /// The type of the fifth delegate parameter. - /// The type of the sixth delegate parameter. - /// The delegate to obtain the method information from. - /// A parameter used only for type inference. - /// A parameter used only for type inference. - /// A parameter used only for type inference. - /// A parameter used only for type inference. - /// A parameter used only for type inference. - /// A parameter used only for type inference. - /// The of the delegate. - public static MethodInfo GetMethodInfo(Action f, T1 unused1, T2 unused2, T3 unused3, T4 unused4, T5 unused5, T6 unused6) - => f.Method; + /// + /// Retrieves the for the specified delegate. + /// + /// The type of the first delegate parameter. + /// The type of the second delegate parameter. + /// The type of the third delegate parameter. + /// The type of the fourth delegate parameter. + /// The type of the fifth delegate parameter. + /// The type of the sixth delegate parameter. + /// The delegate to obtain the method information from. + /// A parameter used only for type inference. + /// A parameter used only for type inference. + /// A parameter used only for type inference. + /// A parameter used only for type inference. + /// A parameter used only for type inference. + /// A parameter used only for type inference. + /// The of the delegate. + public static MethodInfo GetMethodInfo(Action f, T1 unused1, T2 unused2, T3 unused3, T4 unused4, T5 unused5, T6 unused6) + => f.Method; #pragma warning restore IDE0051 // Remove unused private members - #endregion - } + #endregion } \ No newline at end of file diff --git a/Reflection/DefaultConstructorInfo.cs b/Reflection/DefaultConstructorInfo.cs index 80de9a9d..fee920bd 100644 --- a/Reflection/DefaultConstructorInfo.cs +++ b/Reflection/DefaultConstructorInfo.cs @@ -1,187 +1,186 @@ -namespace Ecng.Reflection +namespace Ecng.Reflection; + +#region Using Directives + +using System; +using System.Globalization; +using System.Reflection; + +using Ecng.Common; + +#endregion + +/// +/// Initializes a new instance of the class. +/// +/// The type. +public class DefaultConstructorInfo(Type type) : ConstructorInfo { - #region Using Directives + #region Private Fields + + private readonly Type _type = type; - using System; - using System.Globalization; - using System.Reflection; + #endregion - using Ecng.Common; + #region DefaultConstructorInfo.ctor() #endregion + #region ConstructorInfo Member + + /// + /// Gets the attributes associated with this method. + /// + /// + /// One of the values. + public override MethodAttributes Attributes => MethodAttributes.Public; + + /// + /// Gets the class that declares this member. + /// + /// + /// The Type object for the class that declares this member. + public override Type DeclaringType => _type; + + /// + /// Gets the name of the current member. + /// + /// + /// A containing the name of this member. + public override string Name => ConstructorInfo.ConstructorName; + + /// + /// Gets a handle to the internal metadata representation of a method. + /// + /// + /// A object. + public override RuntimeMethodHandle MethodHandle => throw new NotSupportedException(); + + /// + /// Gets the class object that was used to obtain this instance of . + /// + /// + /// The Type object through which this object was obtained. + public override Type ReflectedType => _type; + + /// + /// When implemented in a derived class, invokes the constructor reflected by this with the specified arguments, under the constraints of the specified Binder. + /// + /// One of the values that specifies the type of binding. + /// A Binder that defines a set of properties and enables the binding, coercion of argument types, and invocation of members using reflection. If binder is null, then Binder.DefaultBinding is used. + /// An array of type Object used to match the number, order and type of the parameters for this constructor, under the constraints of binder. If this constructor does not require parameters, pass an array with zero elements, as in Object[] parameters = new Object[0]. Any object in this array that is not explicitly initialized with a value will contain the default value for that object type. For reference-type elements, this value is null. For value-type elements, this value is 0, 0.0, or false, depending on the specific element type. + /// A used to govern the coercion of types. If this is null, the for the current thread is used. + /// + /// An instance of the class associated with the constructor. + /// + /// The invoked constructor throws an exception. + /// An incorrect number of parameters was passed. + /// Creation of , , and types is not supported. + /// The constructor is private or protected, and the caller lacks . + /// The class is abstract.-or- The constructor is a class initializer. + /// The parameters array does not contain values that match the types accepted by this constructor, under the constraints of the binder. + /// The caller does not have the necessary code access permissions. + public override object Invoke(BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture) + { + return _type.CreateInstance(); + } + /// - /// Initializes a new instance of the class. + /// When overridden in a derived class, invokes the reflected method or constructor with the given parameters. /// - /// The type. - public class DefaultConstructorInfo(Type type) : ConstructorInfo + /// The object on which to invoke the method or constructor. If a method is static, this argument is ignored. If a constructor is static, this argument must be null or an instance of the class that defines the constructor. + /// A bit mask that is a combination of 0 or more bit flags from . If binder is null, this parameter is assigned the value ; thus, whatever you pass in is ignored. + /// An object that enables the binding, coercion of argument types, invocation of members, and retrieval of objects via reflection. If binder is null, the default binder is used. + /// An argument list for the invoked method or constructor. This is an array of objects with the same number, order, and type as the parameters of the method or constructor to be invoked. If there are no parameters, this should be null.If the method or constructor represented by this instance takes a ByRef parameter, there is no special attribute required for that parameter in order to invoke the method or constructor using this function. Any object in this array that is not explicitly initialized with a value will contain the default value for that object type. For reference-type elements, this value is null. For value-type elements, this value is 0, 0.0, or false, depending on the specific element type. + /// An instance of used to govern the coercion of types. If this is null, the for the current thread is used. (This is necessary to convert a String that represents 1000 to a Double value, for example, since 1000 is represented differently by different cultures.) + /// + /// An Object containing the return value of the invoked method, or null in the case of a constructor, or null if the method's return type is void. Before calling the method or constructor, Invoke checks to see if the user has access permission and verify that the parameters are valid. + /// + /// The caller does not have permission to execute the constructor. + /// The type that declares the method is an open generic type. That is, the property returns true for the declaring type. + /// The invoked method or constructor throws an exception. + /// The parameters array does not have the correct number of arguments. + /// The type of the parameters parameter does not match the signature of the method or constructor reflected by this instance. + /// The obj parameter is null and the method is not static.-or- The method is not declared or inherited by the class of obj. -or-A static constructor is invoked, and obj is neither null nor an instance of the class that declared the constructor. + public override object Invoke(object obj, BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture) { - #region Private Fields - - private readonly Type _type = type; - - #endregion - - #region DefaultConstructorInfo.ctor() - - #endregion - - #region ConstructorInfo Member - - /// - /// Gets the attributes associated with this method. - /// - /// - /// One of the values. - public override MethodAttributes Attributes => MethodAttributes.Public; - - /// - /// Gets the class that declares this member. - /// - /// - /// The Type object for the class that declares this member. - public override Type DeclaringType => _type; - - /// - /// Gets the name of the current member. - /// - /// - /// A containing the name of this member. - public override string Name => ConstructorInfo.ConstructorName; - - /// - /// Gets a handle to the internal metadata representation of a method. - /// - /// - /// A object. - public override RuntimeMethodHandle MethodHandle => throw new NotSupportedException(); - - /// - /// Gets the class object that was used to obtain this instance of . - /// - /// - /// The Type object through which this object was obtained. - public override Type ReflectedType => _type; - - /// - /// When implemented in a derived class, invokes the constructor reflected by this with the specified arguments, under the constraints of the specified Binder. - /// - /// One of the values that specifies the type of binding. - /// A Binder that defines a set of properties and enables the binding, coercion of argument types, and invocation of members using reflection. If binder is null, then Binder.DefaultBinding is used. - /// An array of type Object used to match the number, order and type of the parameters for this constructor, under the constraints of binder. If this constructor does not require parameters, pass an array with zero elements, as in Object[] parameters = new Object[0]. Any object in this array that is not explicitly initialized with a value will contain the default value for that object type. For reference-type elements, this value is null. For value-type elements, this value is 0, 0.0, or false, depending on the specific element type. - /// A used to govern the coercion of types. If this is null, the for the current thread is used. - /// - /// An instance of the class associated with the constructor. - /// - /// The invoked constructor throws an exception. - /// An incorrect number of parameters was passed. - /// Creation of , , and types is not supported. - /// The constructor is private or protected, and the caller lacks . - /// The class is abstract.-or- The constructor is a class initializer. - /// The parameters array does not contain values that match the types accepted by this constructor, under the constraints of the binder. - /// The caller does not have the necessary code access permissions. - public override object Invoke(BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture) - { - return _type.CreateInstance(); - } - - /// - /// When overridden in a derived class, invokes the reflected method or constructor with the given parameters. - /// - /// The object on which to invoke the method or constructor. If a method is static, this argument is ignored. If a constructor is static, this argument must be null or an instance of the class that defines the constructor. - /// A bit mask that is a combination of 0 or more bit flags from . If binder is null, this parameter is assigned the value ; thus, whatever you pass in is ignored. - /// An object that enables the binding, coercion of argument types, invocation of members, and retrieval of objects via reflection. If binder is null, the default binder is used. - /// An argument list for the invoked method or constructor. This is an array of objects with the same number, order, and type as the parameters of the method or constructor to be invoked. If there are no parameters, this should be null.If the method or constructor represented by this instance takes a ByRef parameter, there is no special attribute required for that parameter in order to invoke the method or constructor using this function. Any object in this array that is not explicitly initialized with a value will contain the default value for that object type. For reference-type elements, this value is null. For value-type elements, this value is 0, 0.0, or false, depending on the specific element type. - /// An instance of used to govern the coercion of types. If this is null, the for the current thread is used. (This is necessary to convert a String that represents 1000 to a Double value, for example, since 1000 is represented differently by different cultures.) - /// - /// An Object containing the return value of the invoked method, or null in the case of a constructor, or null if the method's return type is void. Before calling the method or constructor, Invoke checks to see if the user has access permission and verify that the parameters are valid. - /// - /// The caller does not have permission to execute the constructor. - /// The type that declares the method is an open generic type. That is, the property returns true for the declaring type. - /// The invoked method or constructor throws an exception. - /// The parameters array does not have the correct number of arguments. - /// The type of the parameters parameter does not match the signature of the method or constructor reflected by this instance. - /// The obj parameter is null and the method is not static.-or- The method is not declared or inherited by the class of obj. -or-A static constructor is invoked, and obj is neither null nor an instance of the class that declared the constructor. - public override object Invoke(object obj, BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture) - { - return _type.CreateInstance(); - } - - /// - /// When overridden in a derived class, returns the flags. - /// - /// The flags. - public override MethodImplAttributes GetMethodImplementationFlags() - { - return MethodImplAttributes.Runtime; - } - - /// - /// When overridden in a derived class, gets the parameters of the specified method or constructor. - /// - /// - /// An array of type containing information that matches the signature of the method (or constructor) reflected by this instance. - /// - public override ParameterInfo[] GetParameters() - { - return []; - } - - /// - /// When overridden in a derived class, returns an array of custom attributes identified by . - /// - /// The type of attribute to search for. Only attributes that are assignable to this type are returned. - /// Specifies whether to search this member's inheritance chain to find the attributes. - /// - /// An array of custom attributes applied to this member, or an array with zero (0) elements if no attributes have been applied. - /// - /// If is null. - /// The custom attribute type cannot be loaded. - public override object[] GetCustomAttributes(Type attributeType, bool inherit) - { - return []; - } - - /// - /// When overridden in a derived class, returns an array containing all the custom attributes. - /// - /// Specifies whether to search this member's inheritance chain to find the attributes. - /// - /// An array that contains all the custom attributes, or an array with zero elements if no attributes are defined. - /// - public override object[] GetCustomAttributes(bool inherit) - { - return []; - } - - /// - /// When overridden in a derived class, indicates whether one or more instance of is applied to this member. - /// - /// The Type object to which the custom attributes are applied. - /// Specifies whether to search this member's inheritance chain to find the attributes. - /// - /// true if one or more instance of is applied to this member; otherwise false. - /// - public override bool IsDefined(Type attributeType, bool inherit) - { - return false; - } - - #endregion - - #region Object Members - - /// - /// Returns a that represents the current . - /// - /// - /// A that represents the current . - /// - public override string ToString() - { - return typeof(void).Name + " " + Name + "()"; - } - - #endregion + return _type.CreateInstance(); } + + /// + /// When overridden in a derived class, returns the flags. + /// + /// The flags. + public override MethodImplAttributes GetMethodImplementationFlags() + { + return MethodImplAttributes.Runtime; + } + + /// + /// When overridden in a derived class, gets the parameters of the specified method or constructor. + /// + /// + /// An array of type containing information that matches the signature of the method (or constructor) reflected by this instance. + /// + public override ParameterInfo[] GetParameters() + { + return []; + } + + /// + /// When overridden in a derived class, returns an array of custom attributes identified by . + /// + /// The type of attribute to search for. Only attributes that are assignable to this type are returned. + /// Specifies whether to search this member's inheritance chain to find the attributes. + /// + /// An array of custom attributes applied to this member, or an array with zero (0) elements if no attributes have been applied. + /// + /// If is null. + /// The custom attribute type cannot be loaded. + public override object[] GetCustomAttributes(Type attributeType, bool inherit) + { + return []; + } + + /// + /// When overridden in a derived class, returns an array containing all the custom attributes. + /// + /// Specifies whether to search this member's inheritance chain to find the attributes. + /// + /// An array that contains all the custom attributes, or an array with zero elements if no attributes are defined. + /// + public override object[] GetCustomAttributes(bool inherit) + { + return []; + } + + /// + /// When overridden in a derived class, indicates whether one or more instance of is applied to this member. + /// + /// The Type object to which the custom attributes are applied. + /// Specifies whether to search this member's inheritance chain to find the attributes. + /// + /// true if one or more instance of is applied to this member; otherwise false. + /// + public override bool IsDefined(Type attributeType, bool inherit) + { + return false; + } + + #endregion + + #region Object Members + + /// + /// Returns a that represents the current . + /// + /// + /// A that represents the current . + /// + public override string ToString() + { + return typeof(void).Name + " " + Name + "()"; + } + + #endregion } \ No newline at end of file diff --git a/Reflection/LazyHelper.cs b/Reflection/LazyHelper.cs index c34f22d2..930248cb 100644 --- a/Reflection/LazyHelper.cs +++ b/Reflection/LazyHelper.cs @@ -1,120 +1,119 @@ -namespace Ecng.Reflection -{ - using System; - using System.Reflection; +namespace Ecng.Reflection; - using Ecng.Common; - using Ecng.Collections; +using System; +using System.Reflection; - /// - /// Lazy helper. - /// - public static class LazyHelper +using Ecng.Common; +using Ecng.Collections; + +/// +/// Lazy helper. +/// +public static class LazyHelper +{ + private static readonly SynchronizedDictionary _factories = []; + private static readonly SynchronizedDictionary _states = []; + + private class Holder { - private static readonly SynchronizedDictionary _factories = []; - private static readonly SynchronizedDictionary _states = []; + private static readonly FieldInfo _valueFactory; + private static readonly FieldInfo _boxed; + private static readonly FieldInfo _state; + private static readonly FieldInfo _factory; + private static readonly FieldInfo _value; - private class Holder + static Holder() { - private static readonly FieldInfo _valueFactory; - private static readonly FieldInfo _boxed; - private static readonly FieldInfo _state; - private static readonly FieldInfo _factory; - private static readonly FieldInfo _value; + static FieldInfo GetField(string name) + => typeof(Lazy).GetField(name, ReflectionHelper.AllInstanceMembers); - static Holder() + if (OperatingSystemEx.IsFramework) { - static FieldInfo GetField(string name) - => typeof(Lazy).GetField(name, ReflectionHelper.AllInstanceMembers); - - if (OperatingSystemEx.IsFramework) - { - _valueFactory = GetField("m_valueFactory"); - _boxed = GetField("m_boxed"); - } - else - { - _state = GetField("_state"); - _factory = GetField("_factory"); - _value = GetField("_value"); - } + _valueFactory = GetField("m_valueFactory"); + _boxed = GetField("m_boxed"); } - - public static Lazy Track(Lazy lazy) + else { - if (lazy is null) - throw new ArgumentNullException(nameof(lazy)); - - if (lazy.IsValueCreated) - throw new ArgumentException(nameof(lazy)); + _state = GetField("_state"); + _factory = GetField("_factory"); + _value = GetField("_value"); + } + } - if (_valueFactory is not null) - _factories.Add(lazy, (Delegate)_valueFactory.GetValue(lazy)); - else - { - _states.Add(lazy, _state.GetValue(lazy)); - _factories.Add(lazy, (Delegate)_factory.GetValue(lazy)); - } + public static Lazy Track(Lazy lazy) + { + if (lazy is null) + throw new ArgumentNullException(nameof(lazy)); - return lazy; - } + if (lazy.IsValueCreated) + throw new ArgumentException(nameof(lazy)); - public static void Reset(Lazy lazy) + if (_valueFactory is not null) + _factories.Add(lazy, (Delegate)_valueFactory.GetValue(lazy)); + else { - if (lazy is null) - throw new ArgumentNullException(nameof(lazy)); - - var factory = _factories[lazy]; - - if (_valueFactory is not null) - { - _boxed.SetValue(lazy, null); - _valueFactory.SetValue(lazy, factory); - } - else - { - _state.SetValue(lazy, _states[lazy]); - _factory.SetValue(lazy, factory); - } + _states.Add(lazy, _state.GetValue(lazy)); + _factories.Add(lazy, (Delegate)_factory.GetValue(lazy)); } - public static void SetValue(Lazy lazy, T value) - { - if (lazy is null) - throw new ArgumentNullException(nameof(lazy)); + return lazy; + } + + public static void Reset(Lazy lazy) + { + if (lazy is null) + throw new ArgumentNullException(nameof(lazy)); - if (_value is null) - throw new PlatformNotSupportedException(); + var factory = _factories[lazy]; - _state.SetValue(lazy, null); - _value.SetValue(lazy, value); + if (_valueFactory is not null) + { + _boxed.SetValue(lazy, null); + _valueFactory.SetValue(lazy, factory); + } + else + { + _state.SetValue(lazy, _states[lazy]); + _factory.SetValue(lazy, factory); } } - /// - /// Tracks the lazy. - /// - /// The type of the lazy. - /// The lazy. - /// The lazy. - public static Lazy Track(this Lazy lazy) - => Holder.Track(lazy); - - /// - /// Resets the lazy. - /// - /// The type of the lazy. - /// The lazy. - public static void Reset(this Lazy lazy) - => Holder.Reset(lazy); - - /// - /// Sets the value. - /// - /// The type of the lazy. - /// The lazy. - /// Value. - public static void SetValue(this Lazy lazy, T value) - => Holder.SetValue(lazy, value); + public static void SetValue(Lazy lazy, T value) + { + if (lazy is null) + throw new ArgumentNullException(nameof(lazy)); + + if (_value is null) + throw new PlatformNotSupportedException(); + + _state.SetValue(lazy, null); + _value.SetValue(lazy, value); + } } + + /// + /// Tracks the lazy. + /// + /// The type of the lazy. + /// The lazy. + /// The lazy. + public static Lazy Track(this Lazy lazy) + => Holder.Track(lazy); + + /// + /// Resets the lazy. + /// + /// The type of the lazy. + /// The lazy. + public static void Reset(this Lazy lazy) + => Holder.Reset(lazy); + + /// + /// Sets the value. + /// + /// The type of the lazy. + /// The lazy. + /// Value. + public static void SetValue(this Lazy lazy, T value) + => Holder.SetValue(lazy, value); } \ No newline at end of file diff --git a/Reflection/MemberSignature.cs b/Reflection/MemberSignature.cs index f548f0dc..3a584b06 100644 --- a/Reflection/MemberSignature.cs +++ b/Reflection/MemberSignature.cs @@ -1,78 +1,77 @@ -namespace Ecng.Reflection +namespace Ecng.Reflection; + +#region Using Directives + +using System; +using System.Reflection; +using System.Linq; + +using Ecng.Collections; +using Ecng.Common; + +#endregion + +/// +/// Member signature. +/// +[Serializable] +public class MemberSignature : Equatable { - #region Using Directives + #region MemberSignature.ctor() - using System; - using System.Reflection; - using System.Linq; + /// + /// Initializes a new instance of the class. + /// + /// The member. + public MemberSignature(MemberInfo member) + { + Member = member ?? throw new ArgumentNullException(nameof(member)); - using Ecng.Collections; - using Ecng.Common; + ReturnType = member is ConstructorInfo ? typeof(void) : member.GetMemberType(); + + if (member is MethodBase mb) + ParamTypes = [.. mb.GetParameterTypes().Select(t => t.type)]; + else if (member.IsIndexer()) + ParamTypes = [.. ((PropertyInfo)member).GetIndexerTypes()]; + else + ParamTypes = []; + } #endregion /// - /// Member signature. + /// Gets the member. /// - [Serializable] - public class MemberSignature : Equatable + public MemberInfo Member { get; } + + /// + /// Gets or sets the type of the return. + /// + /// The type of the return. + public Type ReturnType { get; } + + /// + /// Gets or sets the param types. + /// + /// The param types. + public Type[] ParamTypes { get; } + + /// + protected override bool OnEquals(MemberSignature other) { - #region MemberSignature.ctor() - - /// - /// Initializes a new instance of the class. - /// - /// The member. - public MemberSignature(MemberInfo member) - { - Member = member ?? throw new ArgumentNullException(nameof(member)); - - ReturnType = member is ConstructorInfo ? typeof(void) : member.GetMemberType(); - - if (member is MethodBase mb) - ParamTypes = [.. mb.GetParameterTypes().Select(t => t.type)]; - else if (member.IsIndexer()) - ParamTypes = [.. ((PropertyInfo)member).GetIndexerTypes()]; - else - ParamTypes = []; - } - - #endregion - - /// - /// Gets the member. - /// - public MemberInfo Member { get; } - - /// - /// Gets or sets the type of the return. - /// - /// The type of the return. - public Type ReturnType { get; } - - /// - /// Gets or sets the param types. - /// - /// The param types. - public Type[] ParamTypes { get; } - - /// - protected override bool OnEquals(MemberSignature other) - { - if (ReturnType != other.ReturnType) - return false; - - return ParamTypes.SequenceEqual(other.ParamTypes); - } - - /// - public override int GetHashCode() - => ReturnType.GetHashCode() ^ ParamTypes.GetHashCodeEx(); - - /// - public override MemberSignature Clone() => new(Member); - - /// - public override string ToString() => Member.ToString(); + if (ReturnType != other.ReturnType) + return false; + + return ParamTypes.SequenceEqual(other.ParamTypes); } + + /// + public override int GetHashCode() + => ReturnType.GetHashCode() ^ ParamTypes.GetHashCodeEx(); + + /// + public override MemberSignature Clone() => new(Member); + + /// + public override string ToString() => Member.ToString(); } \ No newline at end of file diff --git a/Reflection/ReflectionHelper.cs b/Reflection/ReflectionHelper.cs index 5533c22b..b6c5ba86 100644 --- a/Reflection/ReflectionHelper.cs +++ b/Reflection/ReflectionHelper.cs @@ -1,1213 +1,1212 @@ -namespace Ecng.Reflection +namespace Ecng.Reflection; + +using System; +using System.ComponentModel; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using System.Linq; +using System.IO; +using System.Runtime.CompilerServices; + +using Ecng.Collections; +using Ecng.Common; + +/// +/// Provides helper methods for reflection. +/// +public static class ReflectionHelper { - using System; - using System.ComponentModel; - using System.Collections; - using System.Collections.Generic; - using System.Reflection; - using System.Linq; - using System.IO; - using System.Runtime.CompilerServices; - - using Ecng.Collections; - using Ecng.Common; + /// + /// Attribute targets for fields and properties. + /// + public const AttributeTargets Members = AttributeTargets.Field | AttributeTargets.Property; /// - /// Provides helper methods for reflection. + /// Attribute targets for classes, structs, and interfaces. /// - public static class ReflectionHelper - { - /// - /// Attribute targets for fields and properties. - /// - public const AttributeTargets Members = AttributeTargets.Field | AttributeTargets.Property; - - /// - /// Attribute targets for classes, structs, and interfaces. - /// - public const AttributeTargets Types = AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface; - - /// - /// Binding flags for all static members. - /// - public const BindingFlags AllStaticMembers = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; - - /// - /// Binding flags for all instance members. - /// - public const BindingFlags AllInstanceMembers = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; - - /// - /// Binding flags for all members. - /// - public const BindingFlags AllMembers = AllStaticMembers | AllInstanceMembers; - - #region Public Methods - - /// - /// Gets the invoke method from the specified delegate type. - /// - /// The delegate type. - /// The MethodInfo representing the Invoke method. - public static MethodInfo GetInvokeMethod(this Type delegType) - => delegType.GetMethod("Invoke"); - - #endregion - - #region ProxyTypes - - private static readonly Dictionary _proxyTypes = []; - - /// - /// Gets the proxy types. - /// - public static IDictionary ProxyTypes => _proxyTypes; - - #endregion - - /// - /// Determines whether the specified parameter is a params array. - /// - /// The parameter info. - /// True if the parameter is marked as params; otherwise, false. - public static bool IsParams(this ParameterInfo pi) - => pi.GetAttribute() != null; - - #region GetParameterTypes - - /// - /// Gets the parameter types for the specified method. - /// - /// The method base. - /// An array of tuples with parameter info and its type. - public static (ParameterInfo info, Type type)[] GetParameterTypes(this MethodBase method) - => method.GetParameterTypes(false); - - /// - /// Gets the parameter types for the specified method. - /// - /// The method base. - /// True to remove reference type wrappers. - /// An array of tuples with parameter info and its type. - public static (ParameterInfo info, Type type)[] GetParameterTypes(this MethodBase method, bool removeRef) - { - if (method is null) - throw new ArgumentNullException(nameof(method)); + public const AttributeTargets Types = AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface; - return [.. method.GetParameters().Select(param => - { - Type paramType; + /// + /// Binding flags for all static members. + /// + public const BindingFlags AllStaticMembers = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; - if (removeRef && IsOutput(param)) - paramType = param.ParameterType.GetElementType(); - else - paramType = param.ParameterType; + /// + /// Binding flags for all instance members. + /// + public const BindingFlags AllInstanceMembers = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; - return (param, paramType); - })]; - } + /// + /// Binding flags for all members. + /// + public const BindingFlags AllMembers = AllStaticMembers | AllInstanceMembers; - #endregion + #region Public Methods - #region GetGenericType + /// + /// Gets the invoke method from the specified delegate type. + /// + /// The delegate type. + /// The MethodInfo representing the Invoke method. + public static MethodInfo GetInvokeMethod(this Type delegType) + => delegType.GetMethod("Invoke"); - private static readonly SynchronizedDictionary<(Type, Type), Type> _genericTypeCache = []; + #endregion - /// - /// Gets the generic type from the target type based on the provided generic type definition. - /// - /// The target type. - /// The generic type definition. - /// The found generic type or null if not found. - public static Type GetGenericType(this Type targetType, Type genericType) - { - return _genericTypeCache.GetFromCache(new(targetType, genericType), key => key.Item1.GetGenericTypeInternal(key.Item2)); - } + #region ProxyTypes - private static Type GetGenericTypeInternal(this Type targetType, Type genericType) - { - if (targetType is null) - throw new ArgumentNullException(nameof(targetType)); + private static readonly Dictionary _proxyTypes = []; - if (genericType is null) - throw new ArgumentNullException(nameof(genericType)); + /// + /// Gets the proxy types. + /// + public static IDictionary ProxyTypes => _proxyTypes; - if (!genericType.IsGenericTypeDefinition) - throw new ArgumentException(nameof(genericType)); + #endregion - if (targetType.IsGenericType && targetType.GetGenericTypeDefinition() == genericType) - return targetType; - else - { - if (genericType.IsInterface) - { - var findedInterfaces = targetType.GetInterfaces() - .Where(@interface => @interface.IsGenericType && @interface.GetGenericTypeDefinition() == genericType) - .ToList(); - - if (findedInterfaces.Count > 1) - throw new AmbiguousMatchException("Too many interfaces were found."); - else if (findedInterfaces.Count == 1) - return findedInterfaces[0]; - else - return null; - } - else - { - return targetType.BaseType != null ? GetGenericType(targetType.BaseType, genericType) : null; - } - } - } + /// + /// Determines whether the specified parameter is a params array. + /// + /// The parameter info. + /// True if the parameter is marked as params; otherwise, false. + public static bool IsParams(this ParameterInfo pi) + => pi.GetAttribute() != null; - #endregion + #region GetParameterTypes + + /// + /// Gets the parameter types for the specified method. + /// + /// The method base. + /// An array of tuples with parameter info and its type. + public static (ParameterInfo info, Type type)[] GetParameterTypes(this MethodBase method) + => method.GetParameterTypes(false); - #region GetGenericTypeArg + /// + /// Gets the parameter types for the specified method. + /// + /// The method base. + /// True to remove reference type wrappers. + /// An array of tuples with parameter info and its type. + public static (ParameterInfo info, Type type)[] GetParameterTypes(this MethodBase method, bool removeRef) + { + if (method is null) + throw new ArgumentNullException(nameof(method)); - /// - /// Gets the specific generic argument type from the target type. - /// - /// The target type. - /// The generic type definition. - /// The index of the generic argument. - /// The generic argument type. - public static Type GetGenericTypeArg(this Type targetType, Type genericType, int index) + return [.. method.GetParameters().Select(param => { - genericType = GetGenericType(targetType, genericType); + Type paramType; - if (genericType is null) - throw new ArgumentException(nameof(targetType)); + if (removeRef && IsOutput(param)) + paramType = param.ParameterType.GetElementType(); else - return genericType.GetGenericArguments()[index]; - } + paramType = param.ParameterType; - #endregion + return (param, paramType); + })]; + } - #region GetIndexer + #endregion - /// - /// The name for indexer members. - /// - public const string IndexerName = "Item"; + #region GetGenericType - /// - /// Gets the default indexer property of the specified type. - /// - /// The type to search. - /// Additional types to match. - /// The PropertyInfo of the indexer. - public static PropertyInfo GetIndexer(this Type type, params Type[] additionalTypes) - { - return GetMember(type, IndexerName, AllInstanceMembers, default, additionalTypes); - } + private static readonly SynchronizedDictionary<(Type, Type), Type> _genericTypeCache = []; - #endregion + /// + /// Gets the generic type from the target type based on the provided generic type definition. + /// + /// The target type. + /// The generic type definition. + /// The found generic type or null if not found. + public static Type GetGenericType(this Type targetType, Type genericType) + { + return _genericTypeCache.GetFromCache(new(targetType, genericType), key => key.Item1.GetGenericTypeInternal(key.Item2)); + } + + private static Type GetGenericTypeInternal(this Type targetType, Type genericType) + { + if (targetType is null) + throw new ArgumentNullException(nameof(targetType)); + + if (genericType is null) + throw new ArgumentNullException(nameof(genericType)); - #region GetIndexers + if (!genericType.IsGenericTypeDefinition) + throw new ArgumentException(nameof(genericType)); - /// - /// Gets all indexer properties of the specified type. - /// - /// The type to search. - /// Additional types to match. - /// An array with the indexer properties. - public static PropertyInfo[] GetIndexers(this Type type, params Type[] additionalTypes) + if (targetType.IsGenericType && targetType.GetGenericTypeDefinition() == genericType) + return targetType; + else { - return GetMembers(type, AllInstanceMembers, true, IndexerName, default, additionalTypes); + if (genericType.IsInterface) + { + var findedInterfaces = targetType.GetInterfaces() + .Where(@interface => @interface.IsGenericType && @interface.GetGenericTypeDefinition() == genericType) + .ToList(); + + if (findedInterfaces.Count > 1) + throw new AmbiguousMatchException("Too many interfaces were found."); + else if (findedInterfaces.Count == 1) + return findedInterfaces[0]; + else + return null; + } + else + { + return targetType.BaseType != null ? GetGenericType(targetType.BaseType, genericType) : null; + } } + } - #endregion + #endregion - #region GetArgTypes + #region GetGenericTypeArg - /// - /// Gets the argument types from the specified argument. - /// - /// The type of the argument. - /// The argument. - /// An array of argument types. - public static Type[] GetArgTypes(TArg arg) - { - return arg.IsNull() ? Type.EmptyTypes : arg.To(); - } + /// + /// Gets the specific generic argument type from the target type. + /// + /// The target type. + /// The generic type definition. + /// The index of the generic argument. + /// The generic argument type. + public static Type GetGenericTypeArg(this Type targetType, Type genericType, int index) + { + genericType = GetGenericType(targetType, genericType); - #endregion + if (genericType is null) + throw new ArgumentException(nameof(targetType)); + else + return genericType.GetGenericArguments()[index]; + } - #region GetMember + #endregion - /// - /// Gets a constructor member info from the specified type. - /// - /// The member type. - /// The target type. - /// Additional types to match. - /// The member info. - public static T GetMember(this Type type, params Type[] additionalTypes) - where T : ConstructorInfo - { - return type.GetMember(".ctor", AllInstanceMembers, default, additionalTypes); - } + #region GetIndexer - /// - /// Gets a member info by name from the specified type. - /// - /// The member type. - /// The target type. - /// The name of the member. - /// Additional types to match. - /// The member info. - public static T GetMember(this Type type, string memberName, params Type[] additionalTypes) - where T : MemberInfo - { - return type.GetMember(memberName, AllMembers, default, additionalTypes); - } + /// + /// The name for indexer members. + /// + public const string IndexerName = "Item"; - /// - /// Gets a member info by name with specified binding flags. - /// - /// The member type. - /// The target type. - /// The member name. - /// Binding flags to use for lookup. - /// Indicates if it's a setter. - /// Additional types to match. - /// The member info. - public static T GetMember(this Type type, string memberName, BindingFlags flags, bool? isSetter, params Type[] additionalTypes) - where T : MemberInfo - { - if (type is null) - throw new ArgumentNullException(nameof(type)); + /// + /// Gets the default indexer property of the specified type. + /// + /// The type to search. + /// Additional types to match. + /// The PropertyInfo of the indexer. + public static PropertyInfo GetIndexer(this Type type, params Type[] additionalTypes) + { + return GetMember(type, IndexerName, AllInstanceMembers, default, additionalTypes); + } - if (memberName.IsEmpty()) - throw new ArgumentNullException(nameof(memberName)); + #endregion - var members = type.GetMembers(flags, true, memberName, isSetter, additionalTypes); + #region GetIndexers - if (members.Length > 1) - members = [.. FilterMembers(members, isSetter, additionalTypes)]; + /// + /// Gets all indexer properties of the specified type. + /// + /// The type to search. + /// Additional types to match. + /// An array with the indexer properties. + public static PropertyInfo[] GetIndexers(this Type type, params Type[] additionalTypes) + { + return GetMembers(type, AllInstanceMembers, true, IndexerName, default, additionalTypes); + } - if (members.Length != 1) - { - if (members.Length == 2 && members[0] is EventInfo && members[1] is FieldInfo) - return members[1]; + #endregion - throw new ArgumentException($"Type '{type}' has '{members.Length}' members with name '{memberName}'."); - } + #region GetArgTypes - return members[0]; - } + /// + /// Gets the argument types from the specified argument. + /// + /// The type of the argument. + /// The argument. + /// An array of argument types. + public static Type[] GetArgTypes(TArg arg) + { + return arg.IsNull() ? Type.EmptyTypes : arg.To(); + } - #endregion + #endregion - #region GetMembers + #region GetMember - /// - /// Gets members from the type that match the additional types. - /// - /// The member type. - /// The target type. - /// Additional types to match. - /// An array of matched members. - public static T[] GetMembers(this Type type, params Type[] additionalTypes) - where T : MemberInfo - { - return type.GetMembers(AllMembers, additionalTypes); - } + /// + /// Gets a constructor member info from the specified type. + /// + /// The member type. + /// The target type. + /// Additional types to match. + /// The member info. + public static T GetMember(this Type type, params Type[] additionalTypes) + where T : ConstructorInfo + { + return type.GetMember(".ctor", AllInstanceMembers, default, additionalTypes); + } - /// - /// Gets members with specified binding flags that match the additional types. - /// - /// The member type. - /// The target type. - /// Binding flags for lookup. - /// Additional types to match. - /// An array of matched members. - public static T[] GetMembers(this Type type, BindingFlags flags, params Type[] additionalTypes) - where T : MemberInfo - { - return type.GetMembers(flags, true, additionalTypes); - } + /// + /// Gets a member info by name from the specified type. + /// + /// The member type. + /// The target type. + /// The name of the member. + /// Additional types to match. + /// The member info. + public static T GetMember(this Type type, string memberName, params Type[] additionalTypes) + where T : MemberInfo + { + return type.GetMember(memberName, AllMembers, default, additionalTypes); + } - /// - /// Gets members with an option for inheritance and additional types matching. - /// - /// The member type. - /// The target type. - /// Binding flags for lookup. - /// True to include inherited members. - /// Additional types to match. - /// An array of matched members. - public static T[] GetMembers(this Type type, BindingFlags flags, bool inheritance, params Type[] additionalTypes) - where T : MemberInfo + /// + /// Gets a member info by name with specified binding flags. + /// + /// The member type. + /// The target type. + /// The member name. + /// Binding flags to use for lookup. + /// Indicates if it's a setter. + /// Additional types to match. + /// The member info. + public static T GetMember(this Type type, string memberName, BindingFlags flags, bool? isSetter, params Type[] additionalTypes) + where T : MemberInfo + { + if (type is null) + throw new ArgumentNullException(nameof(type)); + + if (memberName.IsEmpty()) + throw new ArgumentNullException(nameof(memberName)); + + var members = type.GetMembers(flags, true, memberName, isSetter, additionalTypes); + + if (members.Length > 1) + members = [.. FilterMembers(members, isSetter, additionalTypes)]; + + if (members.Length != 1) { - return type.GetMembers(flags, inheritance, null, default, additionalTypes); + if (members.Length == 2 && members[0] is EventInfo && members[1] is FieldInfo) + return members[1]; + + throw new ArgumentException($"Type '{type}' has '{members.Length}' members with name '{memberName}'."); } - /// - /// Gets members by name with options for inheritance, setter indication, and additional types matching. - /// - /// The member type. - /// The target type. - /// Binding flags for lookup. - /// True to include inherited members. - /// The member name. - /// Indicates if the member is a setter. - /// Additional types to match. - /// An array of matched members. - public static T[] GetMembers(this Type type, BindingFlags flags, bool inheritance, string memberName, bool? isSetter, params Type[] additionalTypes) - where T : MemberInfo - { - if (type is null) - throw new ArgumentNullException(nameof(type)); + return members[0]; + } + + #endregion + + #region GetMembers + + /// + /// Gets members from the type that match the additional types. + /// + /// The member type. + /// The target type. + /// Additional types to match. + /// An array of matched members. + public static T[] GetMembers(this Type type, params Type[] additionalTypes) + where T : MemberInfo + { + return type.GetMembers(AllMembers, additionalTypes); + } + + /// + /// Gets members with specified binding flags that match the additional types. + /// + /// The member type. + /// The target type. + /// Binding flags for lookup. + /// Additional types to match. + /// An array of matched members. + public static T[] GetMembers(this Type type, BindingFlags flags, params Type[] additionalTypes) + where T : MemberInfo + { + return type.GetMembers(flags, true, additionalTypes); + } - if (_proxyTypes.TryGetValue(type, out var proxyType)) - type = proxyType; + /// + /// Gets members with an option for inheritance and additional types matching. + /// + /// The member type. + /// The target type. + /// Binding flags for lookup. + /// True to include inherited members. + /// Additional types to match. + /// An array of matched members. + public static T[] GetMembers(this Type type, BindingFlags flags, bool inheritance, params Type[] additionalTypes) + where T : MemberInfo + { + return type.GetMembers(flags, inheritance, null, default, additionalTypes); + } + + /// + /// Gets members by name with options for inheritance, setter indication, and additional types matching. + /// + /// The member type. + /// The target type. + /// Binding flags for lookup. + /// True to include inherited members. + /// The member name. + /// Indicates if the member is a setter. + /// Additional types to match. + /// An array of matched members. + public static T[] GetMembers(this Type type, BindingFlags flags, bool inheritance, string memberName, bool? isSetter, params Type[] additionalTypes) + where T : MemberInfo + { + if (type is null) + throw new ArgumentNullException(nameof(type)); - var members = type.GetMembers(memberName, flags, inheritance); + if (_proxyTypes.TryGetValue(type, out var proxyType)) + type = proxyType; - if (!members.IsEmpty() && additionalTypes.Length > 0) - members = FilterMembers(members, isSetter, additionalTypes); + var members = type.GetMembers(memberName, flags, inheritance); - return [.. members]; - } + if (!members.IsEmpty() && additionalTypes.Length > 0) + members = FilterMembers(members, isSetter, additionalTypes); - private static IEnumerable GetMembers(this Type type, string memberName, BindingFlags flags, bool inheritance) - where T : MemberInfo - { - var members = new Dictionary<(string, MemberTypes, MemberSignature), ICollection>(); + return [.. members]; + } - if (inheritance) - { - foreach (Type item in type.GetInterfaces().Concat([type] )) - { - var allMembers = memberName.IsEmpty() ? item.GetMembers(flags) : item.GetMember(memberName, flags); + private static IEnumerable GetMembers(this Type type, string memberName, BindingFlags flags, bool inheritance) + where T : MemberInfo + { + var members = new Dictionary<(string, MemberTypes, MemberSignature), ICollection>(); - foreach (var member in allMembers) - { - if (member is T && member is not Type) - members.AddMember(member); - } - } - } - else + if (inheritance) + { + foreach (Type item in type.GetInterfaces().Concat([type] )) { - var allMembers = memberName.IsEmpty() ? type.GetMembers(flags) : type.GetMember(memberName, flags); + var allMembers = memberName.IsEmpty() ? item.GetMembers(flags) : item.GetMember(memberName, flags); foreach (var member in allMembers) { - if (member is T && member is not Type && member.ReflectedType == type) + if (member is T && member is not Type) members.AddMember(member); } } + } + else + { + var allMembers = memberName.IsEmpty() ? type.GetMembers(flags) : type.GetMember(memberName, flags); - if (type.IsValueType && (typeof(T) == typeof(ConstructorInfo) || memberName == ".ctor")) + foreach (var member in allMembers) { - MemberInfo member = new DefaultConstructorInfo(type); - members.AddMember(member); + if (member is T && member is not Type && member.ReflectedType == type) + members.AddMember(member); } + } - if (inheritance) + if (type.IsValueType && (typeof(T) == typeof(ConstructorInfo) || memberName == ".ctor")) + { + MemberInfo member = new DefaultConstructorInfo(type); + members.AddMember(member); + } + + if (inheritance) + { + if (type.BaseType != null) { - if (type.BaseType != null) - { - foreach (var member in type.BaseType.GetMembers(memberName, flags, true)) - members.AddMember(member); - } + foreach (var member in type.BaseType.GetMembers(memberName, flags, true)) + members.AddMember(member); + } - foreach (var pair in members.Where(arg => arg.Value.Count > 1)) + foreach (var pair in members.Where(arg => arg.Value.Count > 1)) + { + var sortedMembers = pair.Value.OrderBy((x, y) => { - var sortedMembers = pair.Value.OrderBy((x, y) => - { - var result = x.ReflectedType.Compare(y.ReflectedType); + var result = x.ReflectedType.Compare(y.ReflectedType); - if (result == 0) - result = x.DeclaringType.Compare(y.DeclaringType); + if (result == 0) + result = x.DeclaringType.Compare(y.DeclaringType); - return result; - }).ToArray(); + return result; + }).ToArray(); - for (var i = 1; i < sortedMembers.Length; i++) - { - members[pair.Key].Remove(sortedMembers[i]); - //members.Remove(pair.Key, sortedMembers[i]); - } - - if (members[pair.Key].IsEmpty()) - members.Remove(pair.Key); + for (var i = 1; i < sortedMembers.Length; i++) + { + members[pair.Key].Remove(sortedMembers[i]); + //members.Remove(pair.Key, sortedMembers[i]); } + + if (members[pair.Key].IsEmpty()) + members.Remove(pair.Key); } + } - var retVal = new List(); + var retVal = new List(); - foreach (var collection in members.Values) - retVal.AddRange(collection); + foreach (var collection in members.Values) + retVal.AddRange(collection); - return retVal; - } + return retVal; + } - private static void AddMember(this Dictionary<(string, MemberTypes, MemberSignature), ICollection> members, MemberInfo member) - { - if (members is null) - throw new ArgumentNullException(nameof(members)); + private static void AddMember(this Dictionary<(string, MemberTypes, MemberSignature), ICollection> members, MemberInfo member) + { + if (members is null) + throw new ArgumentNullException(nameof(members)); - if (member is null) - throw new ArgumentNullException(nameof(member)); + if (member is null) + throw new ArgumentNullException(nameof(member)); - members.SafeAdd(new(member.Name, member.MemberType, new(member)), delegate - { - return []; - }).Add(member.To()); - } + members.SafeAdd(new(member.Name, member.MemberType, new(member)), delegate + { + return []; + }).Add(member.To()); + } - #endregion + #endregion - #region FilterMembers + #region FilterMembers - /// - /// Filters members based on setter indication and additional type parameters. - /// - /// The member type. - /// The collection of members. - /// Indicates if filtering by setter should be applied. - /// Additional types to match. - /// An enumerable of filtered members. - public static IEnumerable FilterMembers(this IEnumerable members, bool? isSetter, params Type[] additionalTypes) - where T : MemberInfo - { - var ms = FilterMembers(members, false, isSetter, additionalTypes); - return ms.IsEmpty() ? FilterMembers(members, true, isSetter, additionalTypes) : ms; - } + /// + /// Filters members based on setter indication and additional type parameters. + /// + /// The member type. + /// The collection of members. + /// Indicates if filtering by setter should be applied. + /// Additional types to match. + /// An enumerable of filtered members. + public static IEnumerable FilterMembers(this IEnumerable members, bool? isSetter, params Type[] additionalTypes) + where T : MemberInfo + { + var ms = FilterMembers(members, false, isSetter, additionalTypes); + return ms.IsEmpty() ? FilterMembers(members, true, isSetter, additionalTypes) : ms; + } - /// - /// Filters members based on inheritance, setter indication, and additional type parameters. - /// - /// The member type. - /// The collection of members. - /// True to use inheritance comparison. - /// Indicates if filtering by setter should be applied. - /// Additional types to match. - /// An enumerable of filtered members. - public static IEnumerable FilterMembers(this IEnumerable members, bool useInheritance, bool? isSetter, params Type[] additionalTypes) - where T : MemberInfo - { - if (members is null) - throw new ArgumentNullException(nameof(members)); + /// + /// Filters members based on inheritance, setter indication, and additional type parameters. + /// + /// The member type. + /// The collection of members. + /// True to use inheritance comparison. + /// Indicates if filtering by setter should be applied. + /// Additional types to match. + /// An enumerable of filtered members. + public static IEnumerable FilterMembers(this IEnumerable members, bool useInheritance, bool? isSetter, params Type[] additionalTypes) + where T : MemberInfo + { + if (members is null) + throw new ArgumentNullException(nameof(members)); - if (additionalTypes is null) - throw new ArgumentNullException(nameof(additionalTypes)); + if (additionalTypes is null) + throw new ArgumentNullException(nameof(additionalTypes)); - return members.Where(arg => + return members.Where(arg => + { + if (IsIndexer(arg) && additionalTypes.Length > 0) { - if (IsIndexer(arg) && additionalTypes.Length > 0) - { - var pi = arg.To(); + var pi = arg.To(); - return GetIndexerTypes(pi).SequenceEqual(isSetter == true ? additionalTypes.Take(additionalTypes.Length - 1) : additionalTypes, (paramType, additionalType) => - { - if (additionalType == typeof(void)) - return true; - else - return paramType.Compare(additionalType, useInheritance); - }); - } - else if (additionalTypes.Length == 1 && (arg is FieldInfo || arg is PropertyInfo || arg is EventInfo)) + return GetIndexerTypes(pi).SequenceEqual(isSetter == true ? additionalTypes.Take(additionalTypes.Length - 1) : additionalTypes, (paramType, additionalType) => { - return GetMemberType(arg).Compare(additionalTypes[0], useInheritance); - } - else if (arg is MethodBase mb) + if (additionalType == typeof(void)) + return true; + else + return paramType.Compare(additionalType, useInheritance); + }); + } + else if (additionalTypes.Length == 1 && (arg is FieldInfo || arg is PropertyInfo || arg is EventInfo)) + { + return GetMemberType(arg).Compare(additionalTypes[0], useInheritance); + } + else if (arg is MethodBase mb) + { + var tuples = mb.GetParameterTypes(true); + + if (tuples.Length > 0 && tuples.Last().info.IsParams()) { - var tuples = mb.GetParameterTypes(true); + // wrap plain params types into object[] + var paramsTypes = additionalTypes.Skip(tuples.Length - 1).ToArray(); - if (tuples.Length > 0 && tuples.Last().info.IsParams()) + if (paramsTypes.Length > 0) { - // wrap plain params types into object[] - var paramsTypes = additionalTypes.Skip(tuples.Length - 1).ToArray(); - - if (paramsTypes.Length > 0) - { - additionalTypes = [.. additionalTypes.Take(tuples.Length - 1), tuples.Last().type]; - } + additionalTypes = [.. additionalTypes.Take(tuples.Length - 1), tuples.Last().type]; } - - return tuples.Select(t => t.type).SequenceEqual(additionalTypes, (paramType, additionalType) => - { - if (additionalType == typeof(void)) - return true; - else - return paramType.Compare(additionalType, useInheritance); - }); } - else - return false; - }); - } - #endregion + return tuples.Select(t => t.type).SequenceEqual(additionalTypes, (paramType, additionalType) => + { + if (additionalType == typeof(void)) + return true; + else + return paramType.Compare(additionalType, useInheritance); + }); + } + else + return false; + }); + } - #region IsAbstract + #endregion - private static readonly SynchronizedDictionary _isAbstractCache = []; + #region IsAbstract - /// - /// Determines whether the specified member is abstract. - /// - /// The member info. - /// True if the member is abstract; otherwise, false. - public static bool IsAbstract(this MemberInfo member) - { - if (member is null) - throw new ArgumentNullException(nameof(member)); + private static readonly SynchronizedDictionary _isAbstractCache = []; + + /// + /// Determines whether the specified member is abstract. + /// + /// The member info. + /// True if the member is abstract; otherwise, false. + public static bool IsAbstract(this MemberInfo member) + { + if (member is null) + throw new ArgumentNullException(nameof(member)); - return _isAbstractCache.GetFromCache(member, delegate + return _isAbstractCache.GetFromCache(member, delegate + { + return member switch { - return member switch - { - MethodBase mb => mb.IsAbstract, - Type type => type.IsAbstract, - PropertyInfo prop => (prop.CanRead && prop.GetGetMethod(true).IsAbstract) || (prop.CanWrite && prop.GetSetMethod(true).IsAbstract), - EventInfo evt => evt.GetAddMethod(true).IsAbstract || evt.GetRemoveMethod(true).IsAbstract, - _ => false, - }; - }); - } + MethodBase mb => mb.IsAbstract, + Type type => type.IsAbstract, + PropertyInfo prop => (prop.CanRead && prop.GetGetMethod(true).IsAbstract) || (prop.CanWrite && prop.GetSetMethod(true).IsAbstract), + EventInfo evt => evt.GetAddMethod(true).IsAbstract || evt.GetRemoveMethod(true).IsAbstract, + _ => false, + }; + }); + } - #endregion + #endregion - #region IsVirtual + #region IsVirtual - private static readonly SynchronizedDictionary _isVirtualCache = []; + private static readonly SynchronizedDictionary _isVirtualCache = []; - /// - /// Determines whether the specified member is virtual. - /// - /// The member info. - /// True if the member is virtual; otherwise, false. - public static bool IsVirtual(this MemberInfo member) + /// + /// Determines whether the specified member is virtual. + /// + /// The member info. + /// True if the member is virtual; otherwise, false. + public static bool IsVirtual(this MemberInfo member) + { + if (member is null) + throw new ArgumentNullException(nameof(member)); + + return _isVirtualCache.GetFromCache(member, delegate { - if (member is null) - throw new ArgumentNullException(nameof(member)); + if (member is MethodBase mb) + return mb.IsVirtual; + //else if (member is Type type) + // return type.IsVirtual; + else if (member is PropertyInfo prop) + return (prop.CanRead && prop.GetGetMethod(true).IsVirtual) || (prop.CanWrite && prop.GetSetMethod(true).IsVirtual); + else if (member is EventInfo evt) + return evt.GetAddMethod(true).IsVirtual || evt.GetRemoveMethod(true).IsVirtual; + else + return false; + }); + } - return _isVirtualCache.GetFromCache(member, delegate - { - if (member is MethodBase mb) - return mb.IsVirtual; - //else if (member is Type type) - // return type.IsVirtual; - else if (member is PropertyInfo prop) - return (prop.CanRead && prop.GetGetMethod(true).IsVirtual) || (prop.CanWrite && prop.GetSetMethod(true).IsVirtual); - else if (member is EventInfo evt) - return evt.GetAddMethod(true).IsVirtual || evt.GetRemoveMethod(true).IsVirtual; - else - return false; - }); - } + #endregion - #endregion + #region IsOverloadable - #region IsOverloadable + /// + /// Determines whether the specified member is overloadable. + /// + /// The member info. + /// True if the member is overloadable; otherwise, false. + public static bool IsOverloadable(this MemberInfo member) + { + if (member is null) + throw new ArgumentNullException(nameof(member)); - /// - /// Determines whether the specified member is overloadable. - /// - /// The member info. - /// True if the member is overloadable; otherwise, false. - public static bool IsOverloadable(this MemberInfo member) - { - if (member is null) - throw new ArgumentNullException(nameof(member)); + return member is ConstructorInfo || member.IsAbstract() || member.IsVirtual(); + } - return member is ConstructorInfo || member.IsAbstract() || member.IsVirtual(); - } + #endregion - #endregion + #region IsIndexer - #region IsIndexer + /// + /// Determines whether the specified member is an indexer. + /// + /// The member info. + /// True if the member is an indexer; otherwise, false. + public static bool IsIndexer(this MemberInfo member) + { + if (member is PropertyInfo prop) + return prop.IsIndexer(); + else + return false; + } - /// - /// Determines whether the specified member is an indexer. - /// - /// The member info. - /// True if the member is an indexer; otherwise, false. - public static bool IsIndexer(this MemberInfo member) - { - if (member is PropertyInfo prop) - return prop.IsIndexer(); - else - return false; - } + /// + /// Determines whether the specified property is an indexer. + /// + /// The property info. + /// True if the property is an indexer; otherwise, false. + public static bool IsIndexer(this PropertyInfo property) + { + if (property is null) + throw new ArgumentNullException(nameof(property)); - /// - /// Determines whether the specified property is an indexer. - /// - /// The property info. - /// True if the property is an indexer; otherwise, false. - public static bool IsIndexer(this PropertyInfo property) - { - if (property is null) - throw new ArgumentNullException(nameof(property)); + return property.GetIndexParameters().Length > 0; + } - return property.GetIndexParameters().Length > 0; - } + #endregion - #endregion + #region GetIndexerTypes - #region GetIndexerTypes + /// + /// Gets the types of the parameters of the indexer property. + /// + /// The indexer property. + /// An enumerable of parameter types. + public static IEnumerable GetIndexerTypes(this PropertyInfo property) + { + if (property is null) + throw new ArgumentNullException(nameof(property)); - /// - /// Gets the types of the parameters of the indexer property. - /// - /// The indexer property. - /// An enumerable of parameter types. - public static IEnumerable GetIndexerTypes(this PropertyInfo property) - { - if (property is null) - throw new ArgumentNullException(nameof(property)); + var accessor = property.GetGetMethod(true) ?? property.GetSetMethod(true); - var accessor = property.GetGetMethod(true) ?? property.GetSetMethod(true); + if (accessor is null) + throw new ArgumentException(nameof(property), "No any accessors."); - if (accessor is null) - throw new ArgumentException(nameof(property), "No any accessors."); + return accessor.GetParameterTypes().Select(t => t.type); + } - return accessor.GetParameterTypes().Select(t => t.type); - } + #endregion - #endregion + #region MemberIs - #region MemberIs + /// + /// Determines whether the member has one of the specified member types. + /// + /// The member info. + /// The member types to check. + /// True if the member matches one of the types; otherwise, false. + public static bool MemberIs(this MemberInfo member, params MemberTypes[] types) + { + if (member is null) + throw new ArgumentNullException(nameof(member)); - /// - /// Determines whether the member has one of the specified member types. - /// - /// The member info. - /// The member types to check. - /// True if the member matches one of the types; otherwise, false. - public static bool MemberIs(this MemberInfo member, params MemberTypes[] types) - { - if (member is null) - throw new ArgumentNullException(nameof(member)); + return types.Any(type => member.MemberType == type); + } - return types.Any(type => member.MemberType == type); - } + #endregion - #endregion + #region IsOutput - #region IsOutput + /// + /// Determines whether the specified parameter is an output parameter. + /// + /// The parameter info. + /// True if the parameter is output; otherwise, false. + public static bool IsOutput(this ParameterInfo param) + { + if (param is null) + throw new ArgumentNullException(nameof(param)); - /// - /// Determines whether the specified parameter is an output parameter. - /// - /// The parameter info. - /// True if the parameter is output; otherwise, false. - public static bool IsOutput(this ParameterInfo param) - { - if (param is null) - throw new ArgumentNullException(nameof(param)); + return param.IsOut || param.ParameterType.IsByRef; + } - return param.IsOut || param.ParameterType.IsByRef; - } + #endregion - #endregion + #region GetMemberType - #region GetMemberType + /// + /// Gets the type associated with the member. + /// + /// The member info. + /// The type of the member. + public static Type GetMemberType(this MemberInfo member) + { + if (member is null) + throw new ArgumentNullException(nameof(member)); - /// - /// Gets the type associated with the member. - /// - /// The member info. - /// The type of the member. - public static Type GetMemberType(this MemberInfo member) + return member switch { - if (member is null) - throw new ArgumentNullException(nameof(member)); + PropertyInfo pi => pi.PropertyType, + FieldInfo fi => fi.FieldType, + MethodInfo mi => mi.ReturnType, + EventInfo ei => ei.EventHandlerType, + ConstructorInfo _ => member.ReflectedType, + _ => throw new ArgumentOutOfRangeException(nameof(member), member.To()), + }; + } - return member switch - { - PropertyInfo pi => pi.PropertyType, - FieldInfo fi => fi.FieldType, - MethodInfo mi => mi.ReturnType, - EventInfo ei => ei.EventHandlerType, - ConstructorInfo _ => member.ReflectedType, - _ => throw new ArgumentOutOfRangeException(nameof(member), member.To()), - }; - } + #endregion - #endregion + #region IsCollection - #region IsCollection + private static readonly SynchronizedDictionary _isCollectionCache = []; - private static readonly SynchronizedDictionary _isCollectionCache = []; + /// + /// Determines whether the specified type is a collection. + /// + /// The type to check. + /// True if the type is a collection; otherwise, false. + public static bool IsCollection(this Type type) + { + if (type is null) + throw new ArgumentNullException(nameof(type)); - /// - /// Determines whether the specified type is a collection. - /// - /// The type to check. - /// True if the type is a collection; otherwise, false. - public static bool IsCollection(this Type type) + return _isCollectionCache.GetFromCache(type, delegate { - if (type is null) - throw new ArgumentNullException(nameof(type)); + return type.Is() + || type.GetGenericType(typeof(ICollection<>)) != null + || type.Is() + || type.GetGenericType(typeof(IEnumerable<>)) != null; + }); - return _isCollectionCache.GetFromCache(type, delegate - { - return type.Is() - || type.GetGenericType(typeof(ICollection<>)) != null - || type.Is() - || type.GetGenericType(typeof(IEnumerable<>)) != null; - }); + } - } + #endregion - #endregion + #region IsStatic - #region IsStatic + private static readonly SynchronizedDictionary _isStaticCache = []; - private static readonly SynchronizedDictionary _isStaticCache = []; + /// + /// Determines whether the specified member is static. + /// + /// The member info. + /// True if the member is static; otherwise, false. + public static bool IsStatic(this MemberInfo member) + { + if (member is null) + throw new ArgumentNullException(nameof(member)); - /// - /// Determines whether the specified member is static. - /// - /// The member info. - /// True if the member is static; otherwise, false. - public static bool IsStatic(this MemberInfo member) + return _isStaticCache.GetFromCache(member, delegate { - if (member is null) - throw new ArgumentNullException(nameof(member)); - - return _isStaticCache.GetFromCache(member, delegate + if (member is MethodBase mb) + return mb.IsStatic; + else if (member is PropertyInfo prop) { - if (member is MethodBase mb) - return mb.IsStatic; - else if (member is PropertyInfo prop) - { - if (prop.CanRead) - return IsStatic(prop.GetGetMethod(true)); - else if (prop.CanWrite) - return IsStatic(prop.GetSetMethod(true)); - else - throw new ArgumentOutOfRangeException(nameof(member), member.To()); - } - else if (member is FieldInfo fi) - return fi.IsStatic; - else if (member is EventInfo evt) - return IsStatic(evt.GetAddMethod(true)); - else if (member is Type type) - return type.IsAbstract && type.IsSealed; + if (prop.CanRead) + return IsStatic(prop.GetGetMethod(true)); + else if (prop.CanWrite) + return IsStatic(prop.GetSetMethod(true)); else throw new ArgumentOutOfRangeException(nameof(member), member.To()); - }); - } + } + else if (member is FieldInfo fi) + return fi.IsStatic; + else if (member is EventInfo evt) + return IsStatic(evt.GetAddMethod(true)); + else if (member is Type type) + return type.IsAbstract && type.IsSealed; + else + throw new ArgumentOutOfRangeException(nameof(member), member.To()); + }); + } - #endregion + #endregion - #region GetItemType + #region GetItemType - [MethodImpl(MethodImplOptions.NoInlining)] - private static Type TryGetAsyncEnumerableItem(this Type collectionType) + [MethodImpl(MethodImplOptions.NoInlining)] + private static Type TryGetAsyncEnumerableItem(this Type collectionType) + { + try { - try - { - return collectionType.GetGenericType(typeof(IAsyncEnumerable<>)); - } - catch (FileNotFoundException) - { - return null; - } + return collectionType.GetGenericType(typeof(IAsyncEnumerable<>)); } - - private static readonly SynchronizedDictionary _getItemTypeCache = []; - - /// - /// Gets the item type for a collection type. - /// - /// The collection type. - /// The item type contained in the collection. - public static Type GetItemType(this Type collectionType) + catch (FileNotFoundException) { - if (collectionType is null) - throw new ArgumentNullException(nameof(collectionType)); + return null; + } + } - return _getItemTypeCache.GetFromCache(collectionType, delegate - { - var interfaceType = - collectionType.GetGenericType(typeof(ICollection<>)) ?? - collectionType.GetGenericType(typeof(IEnumerable<>)) ?? - collectionType.TryGetAsyncEnumerableItem(); + private static readonly SynchronizedDictionary _getItemTypeCache = []; - if (interfaceType != null) - return interfaceType.GetGenericArguments()[0]; - else - throw new InvalidOperationException($"Type '{collectionType}' isn't collection."); - }); - } + /// + /// Gets the item type for a collection type. + /// + /// The collection type. + /// The item type contained in the collection. + public static Type GetItemType(this Type collectionType) + { + if (collectionType is null) + throw new ArgumentNullException(nameof(collectionType)); - #endregion - - /// - /// Getters prefix. - /// - public const string GetPrefix = "get_"; - - /// - /// Setters prefix. - /// - public const string SetPrefix = "set_"; - - /// - /// Adders prefix. - /// - public const string AddPrefix = "add_"; - - /// - /// Removers prefix. - /// - public const string RemovePrefix = "remove_"; - - #region MakePropertyName - - /// - /// Makes the property name from the accessor name. - /// - /// The accessor name. - /// - /// The property name. - public static string MakePropertyName(this string accessorName) + return _getItemTypeCache.GetFromCache(collectionType, delegate { - return accessorName.ThrowIfEmpty(nameof(accessorName)) - .Remove(GetPrefix) - .Remove(SetPrefix) - .Remove(AddPrefix) - .Remove(RemovePrefix); - } - - #endregion + var interfaceType = + collectionType.GetGenericType(typeof(ICollection<>)) ?? + collectionType.GetGenericType(typeof(IEnumerable<>)) ?? + collectionType.TryGetAsyncEnumerableItem(); - #region GetAccessorOwner + if (interfaceType != null) + return interfaceType.GetGenericArguments()[0]; + else + throw new InvalidOperationException($"Type '{collectionType}' isn't collection."); + }); + } - private static readonly SynchronizedDictionary _getAccessorOwnerCache = []; + #endregion - /// - /// Gets the owner of the accessor method. - /// - /// - /// - public static MemberInfo GetAccessorOwner(this MethodInfo method) - { - if (method is null) - throw new ArgumentNullException(nameof(method)); + /// + /// Getters prefix. + /// + public const string GetPrefix = "get_"; - return _getAccessorOwnerCache.GetFromCache(method, delegate - { - var flags = method.IsStatic ? AllStaticMembers : AllInstanceMembers; + /// + /// Setters prefix. + /// + public const string SetPrefix = "set_"; - if (method.Name.Contains(GetPrefix) || method.Name.Contains(SetPrefix)) - { - var name = MakePropertyName(method.Name); + /// + /// Adders prefix. + /// + public const string AddPrefix = "add_"; - return GetMembers(method.ReflectedType, flags, true, name, default) - .FirstOrDefault(property => property.GetGetMethod(true) == method || property.GetSetMethod(true) == method); - } - else if (method.Name.Contains(AddPrefix) || method.Name.Contains(RemovePrefix)) - { - var name = MakePropertyName(method.Name); + /// + /// Removers prefix. + /// + public const string RemovePrefix = "remove_"; - return GetMembers(method.ReflectedType, flags, true, name, default) - .FirstOrDefault(@event => @event.GetAddMethod(true) == method || @event.GetRemoveMethod(true) == method); - } + #region MakePropertyName - return null; - }); - } + /// + /// Makes the property name from the accessor name. + /// + /// The accessor name. + /// + /// The property name. + public static string MakePropertyName(this string accessorName) + { + return accessorName.ThrowIfEmpty(nameof(accessorName)) + .Remove(GetPrefix) + .Remove(SetPrefix) + .Remove(AddPrefix) + .Remove(RemovePrefix); + } - #endregion + #endregion - /// - /// Makes the generic method. - /// - /// - /// - /// - public static MethodInfo Make(this MethodInfo method, params Type[] types) - { - if (method is null) - throw new ArgumentNullException(nameof(method)); + #region GetAccessorOwner - return method.MakeGenericMethod(types); - } + private static readonly SynchronizedDictionary _getAccessorOwnerCache = []; - /// - /// Makes the generic method. - /// - /// - /// - public static bool IsRuntimeType(this Type type) - { - return type.BaseType == typeof(Type); - } + /// + /// Gets the owner of the accessor method. + /// + /// + /// + public static MemberInfo GetAccessorOwner(this MethodInfo method) + { + if (method is null) + throw new ArgumentNullException(nameof(method)); - /// - /// Determines whether the specified type is an assembly. - /// - /// - /// - public static bool IsAssembly(this string dllName) + return _getAccessorOwnerCache.GetFromCache(method, delegate { - return dllName.VerifyAssembly() != null; - } + var flags = method.IsStatic ? AllStaticMembers : AllInstanceMembers; - /// - /// Verifies the assembly. - /// - /// - /// - public static AssemblyName VerifyAssembly(this string dllName) - { - try + if (method.Name.Contains(GetPrefix) || method.Name.Contains(SetPrefix)) { - return AssemblyName.GetAssemblyName(dllName); + var name = MakePropertyName(method.Name); + + return GetMembers(method.ReflectedType, flags, true, name, default) + .FirstOrDefault(property => property.GetGetMethod(true) == method || property.GetSetMethod(true) == method); } - catch (BadImageFormatException) + else if (method.Name.Contains(AddPrefix) || method.Name.Contains(RemovePrefix)) { - return null; + var name = MakePropertyName(method.Name); + + return GetMembers(method.ReflectedType, flags, true, name, default) + .FirstOrDefault(@event => @event.GetAddMethod(true) == method || @event.GetRemoveMethod(true) == method); } - } - /// - /// Is cache enabled. - /// - public static bool CacheEnabled { get; set; } = true; + return null; + }); + } - /// - /// Clear cache. - /// - public static void ClearCache() - { - _genericTypeCache.Clear(); - _getAccessorOwnerCache.Clear(); - _getItemTypeCache.Clear(); - _isAbstractCache.Clear(); - _isCollectionCache.Clear(); - _isStaticCache.Clear(); - _isVirtualCache.Clear(); - } + #endregion - private static TValue GetFromCache(this SynchronizedDictionary cache, TKey key, Func createValue) - { - if (!CacheEnabled) - return createValue(key); + /// + /// Makes the generic method. + /// + /// + /// + /// + public static MethodInfo Make(this MethodInfo method, params Type[] types) + { + if (method is null) + throw new ArgumentNullException(nameof(method)); - return cache.SafeAdd(key, createValue); - } + return method.MakeGenericMethod(types); + } + + /// + /// Makes the generic method. + /// + /// + /// + public static bool IsRuntimeType(this Type type) + { + return type.BaseType == typeof(Type); + } - /// - /// Find all implementation in the specified assembly. - /// - /// Filter interface type. - /// Assembly in where types scan required. - /// Show types marked as obsolete. - /// Show non public types. - /// Show types marked as non browsable. - /// Extra filter. - /// Found types. - public static IEnumerable FindImplementations(this Assembly assembly, bool showObsolete = default, bool showNonPublic = default, bool showNonBrowsable = default, Func extraFilter = default) + /// + /// Determines whether the specified type is an assembly. + /// + /// + /// + public static bool IsAssembly(this string dllName) + { + return dllName.VerifyAssembly() != null; + } + + /// + /// Verifies the assembly. + /// + /// + /// + public static AssemblyName VerifyAssembly(this string dllName) + { + try { - if (assembly is null) - throw new ArgumentNullException(nameof(assembly)); - - extraFilter ??= t => true; - - return assembly - .GetTypes() - .Where(t => t.Is() && !t.IsAbstract && !t.IsInterface && - (showNonPublic || t.IsPublic) && - (showObsolete || !t.IsObsolete()) && - (showNonBrowsable || t.IsBrowsable()) && - extraFilter(t)); + return AssemblyName.GetAssemblyName(dllName); } - - /// - /// Is type compatible. - /// - /// Required type. - /// Type. - /// Check result. - public static bool IsRequiredType(this Type type) - => IsRequiredType(type, typeof(T)); - - /// - /// Is type compatible. - /// - /// Type. - /// Required type. - /// Check result. - public static bool IsRequiredType(this Type type, Type required) + catch (BadImageFormatException) { - if (type is null) - throw new ArgumentNullException(nameof(type)); + return null; + } + } - if (required is null) - throw new ArgumentNullException(nameof(required)); + /// + /// Is cache enabled. + /// + public static bool CacheEnabled { get; set; } = true; - return !type.IsAbstract && - type.IsPublic && - !type.IsGenericTypeDefinition && - type.Is(required) && - type.GetConstructor([]) is not null; - } + /// + /// Clear cache. + /// + public static void ClearCache() + { + _genericTypeCache.Clear(); + _getAccessorOwnerCache.Clear(); + _getItemTypeCache.Clear(); + _isAbstractCache.Clear(); + _isCollectionCache.Clear(); + _isStaticCache.Clear(); + _isVirtualCache.Clear(); + } - /// - /// Try find type. - /// - /// Types. - /// Is type compatible. - /// The type name. - /// The found type. - public static Type TryFindType(this IEnumerable types, Func isTypeCompatible, string typeName) - { - if (types is null) - throw new ArgumentNullException(nameof(types)); + private static TValue GetFromCache(this SynchronizedDictionary cache, TKey key, Func createValue) + { + if (!CacheEnabled) + return createValue(key); - if (isTypeCompatible is null && typeName.IsEmpty()) - throw new ArgumentNullException(nameof(typeName)); + return cache.SafeAdd(key, createValue); + } - if (!typeName.IsEmpty()) - return types.FirstOrDefault(t => t.Name.EqualsIgnoreCase(typeName)); - else - return types.FirstOrDefault(isTypeCompatible); - } + /// + /// Find all implementation in the specified assembly. + /// + /// Filter interface type. + /// Assembly in where types scan required. + /// Show types marked as obsolete. + /// Show non public types. + /// Show types marked as non browsable. + /// Extra filter. + /// Found types. + public static IEnumerable FindImplementations(this Assembly assembly, bool showObsolete = default, bool showNonPublic = default, bool showNonBrowsable = default, Func extraFilter = default) + { + if (assembly is null) + throw new ArgumentNullException(nameof(assembly)); + + extraFilter ??= t => true; + + return assembly + .GetTypes() + .Where(t => t.Is() && !t.IsAbstract && !t.IsInterface && + (showNonPublic || t.IsPublic) && + (showObsolete || !t.IsObsolete()) && + (showNonBrowsable || t.IsBrowsable()) && + extraFilter(t)); + } - /// - /// Order the members by declaration. - /// - /// Member type. - /// Members. - /// Ordered members. - public static IEnumerable OrderByDeclaration(this IEnumerable members) - where TMember : MemberInfo - => members.OrderBy(m => m.MetadataToken); - - /// - /// Determines the property is modifiable. - /// - /// The property info. - /// Operations result. - public static bool IsModifiable(this PropertyInfo pi) - => pi.CanWrite && pi.GetAttribute()?.IsReadOnly != true && pi.SetMethod?.IsPublic == true; - - /// - /// Determines the property is match. - /// - /// The property info. - /// - /// Operations result. - public static bool IsMatch(this PropertyInfo propertyInfo, BindingFlags bindingFlags) - { - var hasPublic = bindingFlags.HasFlag(BindingFlags.Public); - var hasNonPublic = bindingFlags.HasFlag(BindingFlags.NonPublic); + /// + /// Is type compatible. + /// + /// Required type. + /// Type. + /// Check result. + public static bool IsRequiredType(this Type type) + => IsRequiredType(type, typeof(T)); - if (!hasPublic || !hasNonPublic) - { - var isPublic = propertyInfo.GetMethod?.IsPublic == true || propertyInfo.SetMethod?.IsPublic == true; + /// + /// Is type compatible. + /// + /// Type. + /// Required type. + /// Check result. + public static bool IsRequiredType(this Type type, Type required) + { + if (type is null) + throw new ArgumentNullException(nameof(type)); - if (hasPublic && !isPublic) - return false; + if (required is null) + throw new ArgumentNullException(nameof(required)); - if (hasNonPublic && isPublic) - return false; - } + return !type.IsAbstract && + type.IsPublic && + !type.IsGenericTypeDefinition && + type.Is(required) && + type.GetConstructor([]) is not null; + } - var hasStatic = bindingFlags.HasFlag(BindingFlags.Static); - var hasInstance = bindingFlags.HasFlag(BindingFlags.Instance); + /// + /// Try find type. + /// + /// Types. + /// Is type compatible. + /// The type name. + /// The found type. + public static Type TryFindType(this IEnumerable types, Func isTypeCompatible, string typeName) + { + if (types is null) + throw new ArgumentNullException(nameof(types)); - if (!hasStatic || !hasInstance) - { - var isStatic = propertyInfo.IsStatic(); + if (isTypeCompatible is null && typeName.IsEmpty()) + throw new ArgumentNullException(nameof(typeName)); - if (hasStatic && !isStatic) - return false; + if (!typeName.IsEmpty()) + return types.FirstOrDefault(t => t.Name.EqualsIgnoreCase(typeName)); + else + return types.FirstOrDefault(isTypeCompatible); + } - if (hasInstance && isStatic) - return false; - } + /// + /// Order the members by declaration. + /// + /// Member type. + /// Members. + /// Ordered members. + public static IEnumerable OrderByDeclaration(this IEnumerable members) + where TMember : MemberInfo + => members.OrderBy(m => m.MetadataToken); - return true; - } + /// + /// Determines the property is modifiable. + /// + /// The property info. + /// Operations result. + public static bool IsModifiable(this PropertyInfo pi) + => pi.CanWrite && pi.GetAttribute()?.IsReadOnly != true && pi.SetMethod?.IsPublic == true; - /// - /// Determines the method is match. - /// - /// The method info. - /// - /// Operations result. - public static bool IsMatch(this MethodBase methodInfo, BindingFlags bindingFlags) + /// + /// Determines the property is match. + /// + /// The property info. + /// + /// Operations result. + public static bool IsMatch(this PropertyInfo propertyInfo, BindingFlags bindingFlags) + { + var hasPublic = bindingFlags.HasFlag(BindingFlags.Public); + var hasNonPublic = bindingFlags.HasFlag(BindingFlags.NonPublic); + + if (!hasPublic || !hasNonPublic) { - var hasPublic = bindingFlags.HasFlag(BindingFlags.Public); - var hasNonPublic = bindingFlags.HasFlag(BindingFlags.NonPublic); + var isPublic = propertyInfo.GetMethod?.IsPublic == true || propertyInfo.SetMethod?.IsPublic == true; - if (!hasPublic || !hasNonPublic) - { - var isPublic = methodInfo.IsPublic; + if (hasPublic && !isPublic) + return false; + + if (hasNonPublic && isPublic) + return false; + } - if (hasPublic && !isPublic) - return false; + var hasStatic = bindingFlags.HasFlag(BindingFlags.Static); + var hasInstance = bindingFlags.HasFlag(BindingFlags.Instance); - if (hasNonPublic && isPublic) - return false; - } + if (!hasStatic || !hasInstance) + { + var isStatic = propertyInfo.IsStatic(); - var hasStatic = bindingFlags.HasFlag(BindingFlags.Static); - var hasInstance = bindingFlags.HasFlag(BindingFlags.Instance); + if (hasStatic && !isStatic) + return false; - if (!hasStatic || !hasInstance) - { - var isStatic = methodInfo.IsStatic; + if (hasInstance && isStatic) + return false; + } - if (hasStatic && !isStatic) - return false; + return true; + } - if (hasInstance && isStatic) - return false; - } + /// + /// Determines the method is match. + /// + /// The method info. + /// + /// Operations result. + public static bool IsMatch(this MethodBase methodInfo, BindingFlags bindingFlags) + { + var hasPublic = bindingFlags.HasFlag(BindingFlags.Public); + var hasNonPublic = bindingFlags.HasFlag(BindingFlags.NonPublic); - return true; + if (!hasPublic || !hasNonPublic) + { + var isPublic = methodInfo.IsPublic; + + if (hasPublic && !isPublic) + return false; + + if (hasNonPublic && isPublic) + return false; } - /// - /// Determines the field is match. - /// - /// The field. - /// - /// Operations result. - public static bool IsMatch(this FieldInfo field, BindingFlags bindingFlags) + var hasStatic = bindingFlags.HasFlag(BindingFlags.Static); + var hasInstance = bindingFlags.HasFlag(BindingFlags.Instance); + + if (!hasStatic || !hasInstance) { - var hasPublic = bindingFlags.HasFlag(BindingFlags.Public); - var hasNonPublic = bindingFlags.HasFlag(BindingFlags.NonPublic); + var isStatic = methodInfo.IsStatic; - if (!hasPublic || !hasNonPublic) - { - var isPublic = field.IsPublic; + if (hasStatic && !isStatic) + return false; - if (hasPublic && !isPublic) - return false; + if (hasInstance && isStatic) + return false; + } - if (hasNonPublic && isPublic) - return false; - } + return true; + } - var hasStatic = bindingFlags.HasFlag(BindingFlags.Static); - var hasInstance = bindingFlags.HasFlag(BindingFlags.Instance); + /// + /// Determines the field is match. + /// + /// The field. + /// + /// Operations result. + public static bool IsMatch(this FieldInfo field, BindingFlags bindingFlags) + { + var hasPublic = bindingFlags.HasFlag(BindingFlags.Public); + var hasNonPublic = bindingFlags.HasFlag(BindingFlags.NonPublic); - if (!hasStatic || !hasInstance) - { - var isStatic = field.IsStatic; + if (!hasPublic || !hasNonPublic) + { + var isPublic = field.IsPublic; - if (hasStatic && !isStatic) - return false; + if (hasPublic && !isPublic) + return false; - if (hasInstance && isStatic) - return false; - } + if (hasNonPublic && isPublic) + return false; + } + + var hasStatic = bindingFlags.HasFlag(BindingFlags.Static); + var hasInstance = bindingFlags.HasFlag(BindingFlags.Instance); - return true; + if (!hasStatic || !hasInstance) + { + var isStatic = field.IsStatic; + + if (hasStatic && !isStatic) + return false; + + if (hasInstance && isStatic) + return false; } + + return true; } } \ No newline at end of file diff --git a/Security/CryptoAlgorithm.cs b/Security/CryptoAlgorithm.cs index 2a028270..ffe95696 100644 --- a/Security/CryptoAlgorithm.cs +++ b/Security/CryptoAlgorithm.cs @@ -1,205 +1,204 @@ -namespace Ecng.Security +namespace Ecng.Security; + +using System; +using System.Security.Cryptography; + +using Ecng.Common; +using Ecng.Security.Cryptographers; + +/// +/// Algorithm types. +/// +public enum AlgorithmTypes +{ + /// + /// Symmetric. + /// + Symmetric, + + /// + /// Asymmetric. + /// + Asymmetric, + + /// + /// Hash. + /// + Hash, +} + +/// +/// Crypto algorithm. +/// +[Serializable] +public class CryptoAlgorithm : Disposable { - using System; - using System.Security.Cryptography; + #region Private Fields + + private readonly SymmetricCryptographer _symmetric; + private readonly AsymmetricCryptographer _asymmetric; + private readonly HashCryptographer _hash; + + #endregion - using Ecng.Common; - using Ecng.Security.Cryptographers; + #region CryptoAlgorithm.ctor() + + /// + /// Initializes a new instance of the class. + /// + /// + public CryptoAlgorithm(SymmetricCryptographer symmetric) + { + _symmetric = symmetric ?? throw new ArgumentNullException(nameof(symmetric)); + } /// - /// Algorithm types. + /// Initializes a new instance of the class. /// - public enum AlgorithmTypes + /// + public CryptoAlgorithm(AsymmetricCryptographer asymmetric) { - /// - /// Symmetric. - /// - Symmetric, - - /// - /// Asymmetric. - /// - Asymmetric, - - /// - /// Hash. - /// - Hash, + _asymmetric = asymmetric ?? throw new ArgumentNullException(nameof(asymmetric)); } /// - /// Crypto algorithm. + /// Initializes a new instance of the class. /// - [Serializable] - public class CryptoAlgorithm : Disposable + /// + public CryptoAlgorithm(HashCryptographer hash) { - #region Private Fields + _hash = hash ?? throw new ArgumentNullException(nameof(hash)); + } - private readonly SymmetricCryptographer _symmetric; - private readonly AsymmetricCryptographer _asymmetric; - private readonly HashCryptographer _hash; + #endregion - #endregion + #region Public Constants - #region CryptoAlgorithm.ctor() + /// + /// The default symmetric algorithm name. + /// + public const string DefaultSymmetricAlgoName = "AES"; - /// - /// Initializes a new instance of the class. - /// - /// - public CryptoAlgorithm(SymmetricCryptographer symmetric) - { - _symmetric = symmetric ?? throw new ArgumentNullException(nameof(symmetric)); - } - - /// - /// Initializes a new instance of the class. - /// - /// - public CryptoAlgorithm(AsymmetricCryptographer asymmetric) - { - _asymmetric = asymmetric ?? throw new ArgumentNullException(nameof(asymmetric)); - } - - /// - /// Initializes a new instance of the class. - /// - /// - public CryptoAlgorithm(HashCryptographer hash) - { - _hash = hash ?? throw new ArgumentNullException(nameof(hash)); - } - - #endregion - - #region Public Constants - - /// - /// The default symmetric algorithm name. - /// - public const string DefaultSymmetricAlgoName = "AES"; - - /// - /// The default asymmetric algorithm name. - /// - public const string DefaultAsymmetricAlgoName = "RSA"; - - /// - /// The default hash algorithm name. - /// - public const string DefaultHashAlgoName = "SHA"; - - #endregion - - #region Create - - /// - /// Creates a symmetric cryptographer. - /// - /// The public key. - /// The symmetric cryptographer. - public static CryptoAlgorithm CreateAssymetricVerifier(byte[] publicKey) - => new(new AsymmetricCryptographer(AsymmetricAlgorithm.Create(DefaultAsymmetricAlgoName), publicKey)); - - /// - /// Creates a symmetric cryptographer. - /// - /// - /// The keys. - /// - public static CryptoAlgorithm Create(AlgorithmTypes type, params byte[][] keys) - { - return type switch - { - AlgorithmTypes.Symmetric => new(new SymmetricCryptographer(SymmetricAlgorithm.Create(DefaultSymmetricAlgoName), keys[0])), - AlgorithmTypes.Asymmetric => new(new AsymmetricCryptographer(AsymmetricAlgorithm.Create(DefaultAsymmetricAlgoName), keys[0], keys[1])), - AlgorithmTypes.Hash => new(keys.Length == 0 ? new HashCryptographer(HashAlgorithm.Create(DefaultHashAlgoName)) : new HashCryptographer(HashAlgorithm.Create(DefaultHashAlgoName), keys[0])), - _ => throw new ArgumentOutOfRangeException(nameof(type), type, "Unknown type."), - }; - } - - #endregion - - #region Encrypt - - /// - /// Encrypts the specified data. - /// - /// The decrypted data. - /// The encrypted data. - public byte[] Encrypt(byte[] data) - { - if (_symmetric is not null) - return _symmetric.Encrypt(data); - else if (_asymmetric is not null) - return _asymmetric.Encrypt(data); - else if (_hash is not null) - return _hash.ComputeHash(data); - else - throw new InvalidOperationException(); - } - - #endregion - - #region Decrypt - - /// - /// Decrypts the specified data. - /// - /// The encrypted data. - /// The decrypted data. - public byte[] Decrypt(byte[] data) - { - if (_symmetric is not null) - return _symmetric.Decrypt(data); - else if (_asymmetric is not null) - return _asymmetric.Decrypt(data); - else if (_hash is not null) - throw new NotSupportedException(); - else - throw new InvalidOperationException(); - } - - #endregion - - /// - /// Computes the hash value of the plaintext. - /// - /// The plaintext in which you wish to hash. - /// The resulting hash. - public byte[] CreateSignature(byte[] data) - { - if (_asymmetric is null) - throw new NotSupportedException(); - - return _asymmetric.CreateSignature(data); - } - - /// - /// Verifies the signature. - /// - /// The data. - /// The signature. - /// - /// true if the signature is valid; otherwise, false. - /// - public bool VerifySignature(byte[] data, byte[] signature) - { - if (_asymmetric is null) - throw new NotSupportedException(); + /// + /// The default asymmetric algorithm name. + /// + public const string DefaultAsymmetricAlgoName = "RSA"; + + /// + /// The default hash algorithm name. + /// + public const string DefaultHashAlgoName = "SHA"; + + #endregion - return _asymmetric.VerifySignature(data, signature); - } + #region Create - #region Disposable Members + /// + /// Creates a symmetric cryptographer. + /// + /// The public key. + /// The symmetric cryptographer. + public static CryptoAlgorithm CreateAssymetricVerifier(byte[] publicKey) + => new(new AsymmetricCryptographer(AsymmetricAlgorithm.Create(DefaultAsymmetricAlgoName), publicKey)); - /// - protected override void DisposeManaged() + /// + /// Creates a symmetric cryptographer. + /// + /// + /// The keys. + /// + public static CryptoAlgorithm Create(AlgorithmTypes type, params byte[][] keys) + { + return type switch { - _symmetric?.Dispose(); - _asymmetric?.Dispose(); - } + AlgorithmTypes.Symmetric => new(new SymmetricCryptographer(SymmetricAlgorithm.Create(DefaultSymmetricAlgoName), keys[0])), + AlgorithmTypes.Asymmetric => new(new AsymmetricCryptographer(AsymmetricAlgorithm.Create(DefaultAsymmetricAlgoName), keys[0], keys[1])), + AlgorithmTypes.Hash => new(keys.Length == 0 ? new HashCryptographer(HashAlgorithm.Create(DefaultHashAlgoName)) : new HashCryptographer(HashAlgorithm.Create(DefaultHashAlgoName), keys[0])), + _ => throw new ArgumentOutOfRangeException(nameof(type), type, "Unknown type."), + }; + } + + #endregion + + #region Encrypt + + /// + /// Encrypts the specified data. + /// + /// The decrypted data. + /// The encrypted data. + public byte[] Encrypt(byte[] data) + { + if (_symmetric is not null) + return _symmetric.Encrypt(data); + else if (_asymmetric is not null) + return _asymmetric.Encrypt(data); + else if (_hash is not null) + return _hash.ComputeHash(data); + else + throw new InvalidOperationException(); + } + + #endregion + + #region Decrypt + + /// + /// Decrypts the specified data. + /// + /// The encrypted data. + /// The decrypted data. + public byte[] Decrypt(byte[] data) + { + if (_symmetric is not null) + return _symmetric.Decrypt(data); + else if (_asymmetric is not null) + return _asymmetric.Decrypt(data); + else if (_hash is not null) + throw new NotSupportedException(); + else + throw new InvalidOperationException(); + } + + #endregion + + /// + /// Computes the hash value of the plaintext. + /// + /// The plaintext in which you wish to hash. + /// The resulting hash. + public byte[] CreateSignature(byte[] data) + { + if (_asymmetric is null) + throw new NotSupportedException(); + + return _asymmetric.CreateSignature(data); + } - #endregion + /// + /// Verifies the signature. + /// + /// The data. + /// The signature. + /// + /// true if the signature is valid; otherwise, false. + /// + public bool VerifySignature(byte[] data, byte[] signature) + { + if (_asymmetric is null) + throw new NotSupportedException(); + + return _asymmetric.VerifySignature(data, signature); } + + #region Disposable Members + + /// + protected override void DisposeManaged() + { + _symmetric?.Dispose(); + _asymmetric?.Dispose(); + } + + #endregion } \ No newline at end of file diff --git a/Security/CryptoHelper.cs b/Security/CryptoHelper.cs index d4859265..cb7cb2a3 100644 --- a/Security/CryptoHelper.cs +++ b/Security/CryptoHelper.cs @@ -1,403 +1,402 @@ -namespace Ecng.Security -{ - using System; - using System.Linq; - using System.IO; - using System.Security.Cryptography; - using System.Security; +namespace Ecng.Security; + +using System; +using System.Linq; +using System.IO; +using System.Security.Cryptography; +using System.Security; - using Ecng.Common; +using Ecng.Common; +/// +/// Crypto helper. +/// +public static class CryptoHelper +{ /// - /// Crypto helper. + /// Converts the to a byte array. /// - public static class CryptoHelper + /// + /// Byte array. + public static byte[] FromRsa(this RSAParameters param) { - /// - /// Converts the to a byte array. - /// - /// - /// Byte array. - public static byte[] FromRsa(this RSAParameters param) - { - var stream = new MemoryStream(); - - WriteByteArray(stream, param.P); - WriteByteArray(stream, param.Q); - WriteByteArray(stream, param.D); - WriteByteArray(stream, param.DP); - WriteByteArray(stream, param.DQ); - WriteByteArray(stream, param.InverseQ); - WriteByteArray(stream, param.Exponent); - WriteByteArray(stream, param.Modulus); - - return stream.To(); - } + var stream = new MemoryStream(); + + WriteByteArray(stream, param.P); + WriteByteArray(stream, param.Q); + WriteByteArray(stream, param.D); + WriteByteArray(stream, param.DP); + WriteByteArray(stream, param.DQ); + WriteByteArray(stream, param.InverseQ); + WriteByteArray(stream, param.Exponent); + WriteByteArray(stream, param.Modulus); + + return stream.To(); + } - /// - /// Converts the byte array to . - /// - /// Byte array. - /// - public static RSAParameters ToRsa(this byte[] key) - { - if (key is null) - throw new ArgumentNullException(nameof(key)); - - var stream = key.To(); - - return new() - { - P = ReadByteArray(stream), - Q = ReadByteArray(stream), - D = ReadByteArray(stream), - DP = ReadByteArray(stream), - DQ = ReadByteArray(stream), - InverseQ = ReadByteArray(stream), - Exponent = ReadByteArray(stream), - Modulus = ReadByteArray(stream) - }; - } + /// + /// Converts the byte array to . + /// + /// Byte array. + /// + public static RSAParameters ToRsa(this byte[] key) + { + if (key is null) + throw new ArgumentNullException(nameof(key)); - #region WriteByteArray + var stream = key.To(); - private static void WriteByteArray(Stream stream, byte[] array) + return new() { - if (stream is null) - throw new ArgumentNullException(nameof(stream)); + P = ReadByteArray(stream), + Q = ReadByteArray(stream), + D = ReadByteArray(stream), + DP = ReadByteArray(stream), + DQ = ReadByteArray(stream), + InverseQ = ReadByteArray(stream), + Exponent = ReadByteArray(stream), + Modulus = ReadByteArray(stream) + }; + } - stream.WriteEx(array is null); + #region WriteByteArray - if (array is null) - return; + private static void WriteByteArray(Stream stream, byte[] array) + { + if (stream is null) + throw new ArgumentNullException(nameof(stream)); - stream.WriteEx(array); - } + stream.WriteEx(array is null); - #endregion + if (array is null) + return; - #region ReadByteArray + stream.WriteEx(array); + } - private static byte[] ReadByteArray(Stream stream) - { - if (stream is null) - throw new ArgumentNullException(nameof(stream)); + #endregion - var isNull = stream.Read(); + #region ReadByteArray - if (isNull) - return null; + private static byte[] ReadByteArray(Stream stream) + { + if (stream is null) + throw new ArgumentNullException(nameof(stream)); - return stream.Read(); - } + var isNull = stream.Read(); - #endregion + if (isNull) + return null; - #region Generate + return stream.Read(); + } - /// - /// Returns a new generated RSAParameters class which - /// will be used as a key for the signature. - /// - /// It will generate a PRIVATE key (which includes the PUBLIC key). - /// - /// - public static RSAParameters GenerateRsa() - { - using var provider = new RSACryptoServiceProvider(); - return provider.ExportParameters(true); - } + #endregion + + #region Generate - #endregion + /// + /// Returns a new generated RSAParameters class which + /// will be used as a key for the signature. + /// + /// It will generate a PRIVATE key (which includes the PUBLIC key). + /// + /// + public static RSAParameters GenerateRsa() + { + using var provider = new RSACryptoServiceProvider(); + return provider.ExportParameters(true); + } - /// - /// Returns the public part of the . - /// - /// - /// The public part of the . - public static RSAParameters PublicPart(this RSAParameters param) + #endregion + + /// + /// Returns the public part of the . + /// + /// + /// The public part of the . + public static RSAParameters PublicPart(this RSAParameters param) + { + return new() { - return new() - { - Exponent = param.Exponent, - Modulus = param.Modulus, - }; - } + Exponent = param.Exponent, + Modulus = param.Modulus, + }; + } - // https://stackoverflow.com/a/10177020/8029915 + // https://stackoverflow.com/a/10177020/8029915 - // This constant is used to determine the keysize of the encryption algorithm in bits. - // We divide this by 8 within the code below to get the equivalent number of bytes. - private const int _keySize = 256; + // This constant is used to determine the keysize of the encryption algorithm in bits. + // We divide this by 8 within the code below to get the equivalent number of bytes. + private const int _keySize = 256; - // This constant determines the number of iterations for the password bytes generation function. - private const int _derivationIterations = 1000; + // This constant determines the number of iterations for the password bytes generation function. + private const int _derivationIterations = 1000; - private static SymmetricAlgorithm CreateRijndaelManaged() - { - var algo = Aes.Create(); + private static SymmetricAlgorithm CreateRijndaelManaged() + { + var algo = Aes.Create(); - algo.BlockSize = 128; - algo.Mode = CipherMode.CBC; - algo.Padding = PaddingMode.PKCS7; + algo.BlockSize = 128; + algo.Mode = CipherMode.CBC; + algo.Padding = PaddingMode.PKCS7; - return algo; - } + return algo; + } - /// - /// Encrypts the plain text. - /// - /// The plain text. - /// The pass phrase. - /// The salt. - /// The iv. - /// The encrypted bytes. - public static byte[] Encrypt(this byte[] plain, string passPhrase, byte[] salt, byte[] iv) - { - if (plain is null) - throw new ArgumentNullException(nameof(plain)); + /// + /// Encrypts the plain text. + /// + /// The plain text. + /// The pass phrase. + /// The salt. + /// The iv. + /// The encrypted bytes. + public static byte[] Encrypt(this byte[] plain, string passPhrase, byte[] salt, byte[] iv) + { + if (plain is null) + throw new ArgumentNullException(nameof(plain)); - if (passPhrase.IsEmpty()) - throw new ArgumentNullException(nameof(passPhrase)); + if (passPhrase.IsEmpty()) + throw new ArgumentNullException(nameof(passPhrase)); - if (iv?.Length > 16) - iv = [.. iv.Take(16)]; + if (iv?.Length > 16) + iv = [.. iv.Take(16)]; - using var password = new Rfc2898DeriveBytes(passPhrase, salt, _derivationIterations); + using var password = new Rfc2898DeriveBytes(passPhrase, salt, _derivationIterations); - var keyBytes = password.GetBytes(_keySize / 8); + var keyBytes = password.GetBytes(_keySize / 8); - using var symmetricKey = CreateRijndaelManaged(); + using var symmetricKey = CreateRijndaelManaged(); - using var encryptor = symmetricKey.CreateEncryptor(keyBytes, iv); - using var memoryStream = new MemoryStream(); - using var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write); + using var encryptor = symmetricKey.CreateEncryptor(keyBytes, iv); + using var memoryStream = new MemoryStream(); + using var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write); - cryptoStream.Write(plain, 0, plain.Length); - cryptoStream.FlushFinalBlock(); - // Create the final bytes as a concatenation of the random salt bytes, the random iv bytes and the cipher bytes. + cryptoStream.Write(plain, 0, plain.Length); + cryptoStream.FlushFinalBlock(); + // Create the final bytes as a concatenation of the random salt bytes, the random iv bytes and the cipher bytes. - return memoryStream.ToArray(); - } + return memoryStream.ToArray(); + } + + private static byte[] ReadStream(this CryptoStream stream, int maxLen) + { + var buffer = new byte[maxLen]; + var offset = 0; - private static byte[] ReadStream(this CryptoStream stream, int maxLen) + while (offset < maxLen) { - var buffer = new byte[maxLen]; - var offset = 0; + var numRead = stream.Read(buffer, offset, maxLen - offset); + if (numRead == 0) + break; - while (offset < maxLen) - { - var numRead = stream.Read(buffer, offset, maxLen - offset); - if (numRead == 0) - break; + offset += numRead; + } - offset += numRead; - } + Array.Resize(ref buffer, offset); - Array.Resize(ref buffer, offset); + return buffer; + } - return buffer; - } + /// + /// Decrypts the cipher text. + /// + /// The cipher text. + /// The pass phrase. + /// The salt. + /// The iv. + /// The decrypted bytes. + public static byte[] Decrypt(this byte[] cipherText, string passPhrase, byte[] salt, byte[] iv) + { + if (cipherText is null) + throw new ArgumentNullException(nameof(cipherText)); - /// - /// Decrypts the cipher text. - /// - /// The cipher text. - /// The pass phrase. - /// The salt. - /// The iv. - /// The decrypted bytes. - public static byte[] Decrypt(this byte[] cipherText, string passPhrase, byte[] salt, byte[] iv) - { - if (cipherText is null) - throw new ArgumentNullException(nameof(cipherText)); + if (passPhrase.IsEmpty()) + throw new ArgumentNullException(nameof(passPhrase)); - if (passPhrase.IsEmpty()) - throw new ArgumentNullException(nameof(passPhrase)); + if (iv?.Length > 16) + iv = [.. iv.Take(16)]; - if (iv?.Length > 16) - iv = [.. iv.Take(16)]; + using var password = new Rfc2898DeriveBytes(passPhrase, salt, _derivationIterations); - using var password = new Rfc2898DeriveBytes(passPhrase, salt, _derivationIterations); + var keyBytes = password.GetBytes(_keySize / 8); - var keyBytes = password.GetBytes(_keySize / 8); + using var symmetricKey = CreateRijndaelManaged(); - using var symmetricKey = CreateRijndaelManaged(); + using var decryptor = symmetricKey.CreateDecryptor(keyBytes, iv); + using var memoryStream = new MemoryStream(cipherText); + using var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read); - using var decryptor = symmetricKey.CreateDecryptor(keyBytes, iv); - using var memoryStream = new MemoryStream(cipherText); - using var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read); + return cryptoStream.ReadStream(cipherText.Length); + } - return cryptoStream.ReadStream(cipherText.Length); - } + private static byte[] TransformAes(bool isEncrypt, byte[] inputBytes, string passPhrase, byte[] salt, byte[] iv) + { + if (inputBytes is null) + throw new ArgumentNullException(nameof(inputBytes)); + + if (passPhrase.IsEmpty()) + throw new ArgumentNullException(nameof(passPhrase)); + + using var password = new Rfc2898DeriveBytes(passPhrase, salt, _derivationIterations); + + var keyBytes = password.GetBytes(_keySize / 8); - private static byte[] TransformAes(bool isEncrypt, byte[] inputBytes, string passPhrase, byte[] salt, byte[] iv) + using var aes = Aes.Create(); + + aes.Mode = CipherMode.CBC; + aes.Padding = PaddingMode.PKCS7; + + if (isEncrypt) { - if (inputBytes is null) - throw new ArgumentNullException(nameof(inputBytes)); + using var encryptor = aes.CreateEncryptor(keyBytes, iv); + using var memoryStream = new MemoryStream(); + using var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write); - if (passPhrase.IsEmpty()) - throw new ArgumentNullException(nameof(passPhrase)); + cryptoStream.Write(inputBytes, 0, inputBytes.Length); + cryptoStream.FlushFinalBlock(); + return memoryStream.ToArray(); + } + else + { + using var decryptor = aes.CreateDecryptor(keyBytes, iv); + using var memoryStream = new MemoryStream(inputBytes); + using var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read); - using var password = new Rfc2898DeriveBytes(passPhrase, salt, _derivationIterations); + return cryptoStream.ReadStream(inputBytes.Length); + } + } - var keyBytes = password.GetBytes(_keySize / 8); + /// + /// Encrypts the plain text. + /// + /// The plain text. + /// The pass phrase. + /// The salt. + /// The iv. + /// The cipher text. + public static byte[] EncryptAes(this byte[] plain, string passPhrase, byte[] salt, byte[] iv) => TransformAes(true, plain, passPhrase, salt, iv); - using var aes = Aes.Create(); + /// + /// Decrypts the cipher text. + /// + /// The cipher text. + /// The pass phrase. + /// The salt. + /// The iv. + /// The plain text. + public static byte[] DecryptAes(this byte[] cipherText, string passPhrase, byte[] salt, byte[] iv) => TransformAes(false, cipherText, passPhrase, salt, iv); + + private static string Hash(this byte[] value, HashAlgorithm algo) + { + if (value is null) + throw new ArgumentNullException(nameof(value)); - aes.Mode = CipherMode.CBC; - aes.Padding = PaddingMode.PKCS7; + if (value.Length == 0) + throw new ArgumentOutOfRangeException(nameof(value)); - if (isEncrypt) - { - using var encryptor = aes.CreateEncryptor(keyBytes, iv); - using var memoryStream = new MemoryStream(); - using var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write); + if (algo is null) + throw new ArgumentNullException(nameof(algo)); - cryptoStream.Write(inputBytes, 0, inputBytes.Length); - cryptoStream.FlushFinalBlock(); - return memoryStream.ToArray(); - } - else - { - using var decryptor = aes.CreateDecryptor(keyBytes, iv); - using var memoryStream = new MemoryStream(inputBytes); - using var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read); + using (algo) + return algo.ComputeHash(value).Digest(); + } - return cryptoStream.ReadStream(inputBytes.Length); - } - } + /// + /// MD5 hash. + /// + /// The value to hash. + /// The hash. + public static string Md5(this byte[] value) + { + return value.Hash(MD5.Create()); + } - /// - /// Encrypts the plain text. - /// - /// The plain text. - /// The pass phrase. - /// The salt. - /// The iv. - /// The cipher text. - public static byte[] EncryptAes(this byte[] plain, string passPhrase, byte[] salt, byte[] iv) => TransformAes(true, plain, passPhrase, salt, iv); - - /// - /// Decrypts the cipher text. - /// - /// The cipher text. - /// The pass phrase. - /// The salt. - /// The iv. - /// The plain text. - public static byte[] DecryptAes(this byte[] cipherText, string passPhrase, byte[] salt, byte[] iv) => TransformAes(false, cipherText, passPhrase, salt, iv); - - private static string Hash(this byte[] value, HashAlgorithm algo) - { - if (value is null) - throw new ArgumentNullException(nameof(value)); + /// + /// SHA1 hash. + /// + /// The value to hash. + /// The hash. + public static string Sha256(this byte[] value) + { + return value.Hash(SHA256.Create()); + } - if (value.Length == 0) - throw new ArgumentOutOfRangeException(nameof(value)); + /// + /// SHA512 hash. + /// + /// The value to hash. + /// The hash. + public static string Sha512(this byte[] value) + { + return value.Hash(SHA512.Create()); + } - if (algo is null) - throw new ArgumentNullException(nameof(algo)); + /// + /// Validates the password. + /// + /// + /// The password to check. + /// Operation result. + public static bool IsValid(this Secret secret, SecureString password) + => secret.IsValid(password.UnSecure()); - using (algo) - return algo.ComputeHash(value).Digest(); - } + /// + /// Validates the password. + /// + /// + /// The password to check. + /// Operation result. + public static bool IsValid(this Secret secret, string password) + => secret.Equals(password.CreateSecret(secret)); - /// - /// MD5 hash. - /// - /// The value to hash. - /// The hash. - public static string Md5(this byte[] value) - { - return value.Hash(MD5.Create()); - } + /// + /// Creates a new from the plain text. + /// + /// The plain text. + /// + public static Secret CreateSecret(this SecureString plainText) + => plainText.UnSecure().CreateSecret(); - /// - /// SHA1 hash. - /// - /// The value to hash. - /// The hash. - public static string Sha256(this byte[] value) - { - return value.Hash(SHA256.Create()); - } + /// + /// Creates a new from the plain text. + /// + /// The plain text. + /// + public static Secret CreateSecret(this string plainText) + => plainText.CreateSecret(TypeHelper.GenerateSalt(Secret.DefaultSaltSize)); - /// - /// SHA512 hash. - /// - /// The value to hash. - /// The hash. - public static string Sha512(this byte[] value) - { - return value.Hash(SHA512.Create()); - } + /// + /// Creates a new from the plain text. + /// + /// The plain text. + /// + /// + public static Secret CreateSecret(this string plainText, Secret secret) + => plainText.CreateSecret(secret.CheckOnNull(nameof(secret)).Salt, secret.Algo); - /// - /// Validates the password. - /// - /// - /// The password to check. - /// Operation result. - public static bool IsValid(this Secret secret, SecureString password) - => secret.IsValid(password.UnSecure()); - - /// - /// Validates the password. - /// - /// - /// The password to check. - /// Operation result. - public static bool IsValid(this Secret secret, string password) - => secret.Equals(password.CreateSecret(secret)); - - /// - /// Creates a new from the plain text. - /// - /// The plain text. - /// - public static Secret CreateSecret(this SecureString plainText) - => plainText.UnSecure().CreateSecret(); - - /// - /// Creates a new from the plain text. - /// - /// The plain text. - /// - public static Secret CreateSecret(this string plainText) - => plainText.CreateSecret(TypeHelper.GenerateSalt(Secret.DefaultSaltSize)); - - /// - /// Creates a new from the plain text. - /// - /// The plain text. - /// - /// - public static Secret CreateSecret(this string plainText, Secret secret) - => plainText.CreateSecret(secret.CheckOnNull(nameof(secret)).Salt, secret.Algo); - - /// - /// Creates a new from the plain text. - /// - /// The plain text. - /// The salt. - /// The hash algorithm. - /// - public static Secret CreateSecret(this string plainText, byte[] salt, CryptoAlgorithm algo = null) - { - if (plainText.IsEmpty()) - throw new ArgumentNullException(nameof(plainText)); + /// + /// Creates a new from the plain text. + /// + /// The plain text. + /// The salt. + /// The hash algorithm. + /// + public static Secret CreateSecret(this string plainText, byte[] salt, CryptoAlgorithm algo = null) + { + if (plainText.IsEmpty()) + throw new ArgumentNullException(nameof(plainText)); - if (salt is null) - throw new ArgumentNullException(nameof(salt)); + if (salt is null) + throw new ArgumentNullException(nameof(salt)); - var unencodedBytes = plainText.Unicode(); - var buffer = new byte[unencodedBytes.Length + salt.Length]; + var unencodedBytes = plainText.Unicode(); + var buffer = new byte[unencodedBytes.Length + salt.Length]; - Buffer.BlockCopy(unencodedBytes, 0, buffer, 0, unencodedBytes.Length); - Buffer.BlockCopy(salt, 0, buffer, unencodedBytes.Length - 1, salt.Length); + Buffer.BlockCopy(unencodedBytes, 0, buffer, 0, unencodedBytes.Length); + Buffer.BlockCopy(salt, 0, buffer, unencodedBytes.Length - 1, salt.Length); - return new Secret(buffer, salt, algo); - } + return new Secret(buffer, salt, algo); } } \ No newline at end of file diff --git a/Security/IAuthorization.cs b/Security/IAuthorization.cs index 44ffe7d3..0a21f975 100644 --- a/Security/IAuthorization.cs +++ b/Security/IAuthorization.cs @@ -1,87 +1,86 @@ -namespace Ecng.Security -{ - using System; - using System.Net; - using System.Security; - using System.Threading; - using System.Threading.Tasks; +namespace Ecng.Security; + +using System; +using System.Net; +using System.Security; +using System.Threading; +using System.Threading.Tasks; - using Ecng.Common; +using Ecng.Common; +/// +/// Defines the interface to an authorization module. +/// +public interface IAuthorization +{ /// - /// Defines the interface to an authorization module. + /// Check login and password. /// - public interface IAuthorization - { - /// - /// Check login and password. - /// - /// Login. - /// Password. - /// Remote network address. - /// Session identifier. - /// - /// - ValueTask ValidateCredentials(string login, SecureString password, IPAddress clientAddress, CancellationToken cancellationToken); - } + /// Login. + /// Password. + /// Remote network address. + /// Session identifier. + /// + /// + ValueTask ValidateCredentials(string login, SecureString password, IPAddress clientAddress, CancellationToken cancellationToken); +} +/// +/// Authorization module granted access for everyone. +/// +public class AnonymousAuthorization : IAuthorization +{ /// - /// Authorization module granted access for everyone. + /// Initializes a new instance of the . /// - public class AnonymousAuthorization : IAuthorization + public AnonymousAuthorization() { - /// - /// Initializes a new instance of the . - /// - public AnonymousAuthorization() - { - } - - /// - public virtual ValueTask ValidateCredentials(string login, SecureString password, IPAddress clientAddress, CancellationToken cancellationToken) - => new(Guid.NewGuid().To()); } + /// + public virtual ValueTask ValidateCredentials(string login, SecureString password, IPAddress clientAddress, CancellationToken cancellationToken) + => new(Guid.NewGuid().To()); +} + +/// +/// The connection access check module which provides access by simple login and password set. +/// +public class SimpleAuthorization : AnonymousAuthorization +{ /// - /// The connection access check module which provides access by simple login and password set. + /// Initializes a new instance of the . /// - public class SimpleAuthorization : AnonymousAuthorization + public SimpleAuthorization() { - /// - /// Initializes a new instance of the . - /// - public SimpleAuthorization() - { - } - - /// - /// Login. - /// - public string Login { get; set; } - - /// - /// Password. - /// - public SecureString Password { get; set; } - - /// - public override ValueTask ValidateCredentials(string login, SecureString password, IPAddress clientAddress, CancellationToken cancellationToken) - { - if (Login.IsEmpty()) - return base.ValidateCredentials(login, password, clientAddress, cancellationToken); - else if (login.EqualsIgnoreCase(Login) && password != null && Password != null && password.IsEqualTo(Password)) - return new(Guid.NewGuid().To()); - - throw new UnauthorizedAccessException(); - } } /// - /// do not allow access. + /// Login. + /// + public string Login { get; set; } + + /// + /// Password. /// - public class UnauthorizedAuthorization : IAuthorization + public SecureString Password { get; set; } + + /// + public override ValueTask ValidateCredentials(string login, SecureString password, IPAddress clientAddress, CancellationToken cancellationToken) { - ValueTask IAuthorization.ValidateCredentials(string login, SecureString password, IPAddress clientAddress, CancellationToken cancellationToken) - => throw new UnauthorizedAccessException(); + if (Login.IsEmpty()) + return base.ValidateCredentials(login, password, clientAddress, cancellationToken); + else if (login.EqualsIgnoreCase(Login) && password != null && Password != null && password.IsEqualTo(Password)) + return new(Guid.NewGuid().To()); + + throw new UnauthorizedAccessException(); } +} + +/// +/// do not allow access. +/// +public class UnauthorizedAuthorization : IAuthorization +{ + ValueTask IAuthorization.ValidateCredentials(string login, SecureString password, IPAddress clientAddress, CancellationToken cancellationToken) + => throw new UnauthorizedAccessException(); } \ No newline at end of file diff --git a/Security/Secret.cs b/Security/Secret.cs index f97cfdd5..11142980 100644 --- a/Security/Secret.cs +++ b/Security/Secret.cs @@ -1,113 +1,112 @@ -namespace Ecng.Security +namespace Ecng.Security; + +using System; +using System.Linq; + +using Ecng.Common; +using Ecng.Collections; + +/// +/// Secret. +/// +public class Secret : Equatable { - using System; - using System.Linq; + /// + /// Initializes a new instance of the class. + /// + public Secret() + { + Algo = CreateDefaultAlgo(); + } - using Ecng.Common; - using Ecng.Collections; + /// + /// Initializes a new instance of the class. + /// + /// + /// The salt. + /// Hash algorithm. Can be . + public Secret(byte[] passwordBytes, byte[] salt, CryptoAlgorithm algo = null) + { + Salt = salt ?? throw new ArgumentNullException(nameof(salt)); + Algo = algo ?? CreateDefaultAlgo(); + Hash = passwordBytes ?? throw new ArgumentNullException(nameof(passwordBytes)); + + Hash = Algo.Encrypt(Hash); + } + + private static CryptoAlgorithm CreateDefaultAlgo() => CryptoAlgorithm.Create(AlgorithmTypes.Hash); + + /// + /// The default salt size. + /// + public const int DefaultSaltSize = 128; /// - /// Secret. + /// Gets or sets the salt. /// - public class Secret : Equatable + /// The salt. + public byte[] Salt { get; set; } + + /// + /// Gets or sets the hash. + /// + /// The hash. + public byte[] Hash { get; set; } + + /// + /// Gets the hash algorithm. + /// + [Newtonsoft.Json.JsonIgnore] + public CryptoAlgorithm Algo { get; } + + /// + protected override bool OnEquals(Secret other) { - /// - /// Initializes a new instance of the class. - /// - public Secret() + if (EnsureGetHashCode() != other.EnsureGetHashCode()) + return false; + + if (Hash is null) { - Algo = CreateDefaultAlgo(); + if (other.Hash != null) + return false; } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The salt. - /// Hash algorithm. Can be . - public Secret(byte[] passwordBytes, byte[] salt, CryptoAlgorithm algo = null) + else { - Salt = salt ?? throw new ArgumentNullException(nameof(salt)); - Algo = algo ?? CreateDefaultAlgo(); - Hash = passwordBytes ?? throw new ArgumentNullException(nameof(passwordBytes)); - - Hash = Algo.Encrypt(Hash); + if (other.Hash is null) + return false; + else if (!Hash.SequenceEqual(other.Hash)) + return false; } - private static CryptoAlgorithm CreateDefaultAlgo() => CryptoAlgorithm.Create(AlgorithmTypes.Hash); - - /// - /// The default salt size. - /// - public const int DefaultSaltSize = 128; - - /// - /// Gets or sets the salt. - /// - /// The salt. - public byte[] Salt { get; set; } - - /// - /// Gets or sets the hash. - /// - /// The hash. - public byte[] Hash { get; set; } - - /// - /// Gets the hash algorithm. - /// - [Newtonsoft.Json.JsonIgnore] - public CryptoAlgorithm Algo { get; } - - /// - protected override bool OnEquals(Secret other) + if (Salt is null) { - if (EnsureGetHashCode() != other.EnsureGetHashCode()) + if (other.Salt != null) return false; - - if (Hash is null) - { - if (other.Hash != null) - return false; - } - else - { - if (other.Hash is null) - return false; - else if (!Hash.SequenceEqual(other.Hash)) - return false; - } - - if (Salt is null) - { - if (other.Salt != null) - return false; - } - else - { - if (other.Salt is null) - return false; - else if (!Salt.SequenceEqual(other.Salt)) - return false; - } - - return true; } - - private int _hashCode; - - private int EnsureGetHashCode() + else { - if (_hashCode == 0) - _hashCode = (Hash?.GetHashCodeEx() ?? 0) ^ (Salt?.GetHashCodeEx() ?? 0); - - return _hashCode; + if (other.Salt is null) + return false; + else if (!Salt.SequenceEqual(other.Salt)) + return false; } - /// - public override int GetHashCode() => EnsureGetHashCode(); + return true; + } + + private int _hashCode; + + private int EnsureGetHashCode() + { + if (_hashCode == 0) + _hashCode = (Hash?.GetHashCodeEx() ?? 0) ^ (Salt?.GetHashCodeEx() ?? 0); - /// - public override Secret Clone() => new() { Hash = Hash?.ToArray(), Salt = Salt?.ToArray() }; + return _hashCode; } + + /// + public override int GetHashCode() => EnsureGetHashCode(); + + /// + public override Secret Clone() => new() { Hash = Hash?.ToArray(), Salt = Salt?.ToArray() }; } \ No newline at end of file diff --git a/Serialization/ContinueOnExceptionContext.cs b/Serialization/ContinueOnExceptionContext.cs index 4ab7ec41..7f73e169 100644 --- a/Serialization/ContinueOnExceptionContext.cs +++ b/Serialization/ContinueOnExceptionContext.cs @@ -1,53 +1,52 @@ -namespace Ecng.Serialization +namespace Ecng.Serialization; + +using System; + +using Ecng.Common; + +/// +/// Context for continue on exception. +/// +public class ContinueOnExceptionContext { - using System; + /// + /// Initializes a new instance of the . + /// + public event Action Error; + + /// + /// Do not encrypt. + /// + public bool DoNotEncrypt { get; set; } - using Ecng.Common; + /// + /// Initializes a new instance of the . + /// + /// The exception. + /// Operation result. + public static bool TryProcess(Exception ex) + { + if (ex is null) + throw new ArgumentNullException(nameof(ex)); + + var ctx = Scope.Current; + + if (ctx is null) + return false; + + ctx.Value.Process(ex); + return true; + } /// - /// Context for continue on exception. + /// Process the exception. /// - public class ContinueOnExceptionContext + /// The exception. + public void Process(Exception ex) { - /// - /// Initializes a new instance of the . - /// - public event Action Error; - - /// - /// Do not encrypt. - /// - public bool DoNotEncrypt { get; set; } - - /// - /// Initializes a new instance of the . - /// - /// The exception. - /// Operation result. - public static bool TryProcess(Exception ex) - { - if (ex is null) - throw new ArgumentNullException(nameof(ex)); - - var ctx = Scope.Current; - - if (ctx is null) - return false; - - ctx.Value.Process(ex); - return true; - } - - /// - /// Process the exception. - /// - /// The exception. - public void Process(Exception ex) - { - if (ex is null) - throw new ArgumentNullException(nameof(ex)); - - Error?.Invoke(ex); - } + if (ex is null) + throw new ArgumentNullException(nameof(ex)); + + Error?.Invoke(ex); } } \ No newline at end of file diff --git a/Serialization/DelayAction.cs b/Serialization/DelayAction.cs index a976b103..fc2e911a 100644 --- a/Serialization/DelayAction.cs +++ b/Serialization/DelayAction.cs @@ -1,580 +1,579 @@ -namespace Ecng.Serialization -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Linq; - using System.Threading; +namespace Ecng.Serialization; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; - using Ecng.Collections; - using Ecng.Common; +using Ecng.Collections; +using Ecng.Common; +/// +/// Provides delayed action execution with batching support. +/// +public class DelayAction : Disposable +{ /// - /// Provides delayed action execution with batching support. + /// Represents a group of delayed actions. /// - public class DelayAction : Disposable + public interface IGroup { /// - /// Represents a group of delayed actions. + /// Adds an action to the group. /// - public interface IGroup - { - /// - /// Adds an action to the group. - /// - /// The action to execute. - /// An action to execute after the main action, with an exception parameter if an error occurred. - /// Determines if the action can be batched. - /// Determines if batching should break on error. - void Add(Action action, Action postAction = null, bool canBatch = true, bool breakBatchOnError = true); + /// The action to execute. + /// An action to execute after the main action, with an exception parameter if an error occurred. + /// Determines if the action can be batched. + /// Determines if batching should break on error. + void Add(Action action, Action postAction = null, bool canBatch = true, bool breakBatchOnError = true); - /// - /// Adds an action that receives an IDisposable scope to the group. - /// - /// The action to execute with the provided scope. - /// An action to execute after the main action, with an exception parameter if an error occurred. - /// Determines if the action can be batched. - /// Determines if batching should break on error. - void Add(Action action, Action postAction = null, bool canBatch = true, bool breakBatchOnError = true); + /// + /// Adds an action that receives an IDisposable scope to the group. + /// + /// The action to execute with the provided scope. + /// An action to execute after the main action, with an exception parameter if an error occurred. + /// Determines if the action can be batched. + /// Determines if batching should break on error. + void Add(Action action, Action postAction = null, bool canBatch = true, bool breakBatchOnError = true); - /// - /// Waits until all actions in the group have been flushed. - /// - /// Determines if the DelayAction instance should be disposed after flushing. - void WaitFlush(bool dispose); - } + /// + /// Waits until all actions in the group have been flushed. + /// + /// Determines if the DelayAction instance should be disposed after flushing. + void WaitFlush(bool dispose); + } + /// + /// Represents a group of delayed actions with a specific group state. + /// + /// The type of the group state, which implements IDisposable. + public interface IGroup : IGroup + where T : IDisposable + { /// - /// Represents a group of delayed actions with a specific group state. + /// Adds an action that receives the group state. /// - /// The type of the group state, which implements IDisposable. - public interface IGroup : IGroup - where T : IDisposable - { - /// - /// Adds an action that receives the group state. - /// - /// The action to execute with the group state. - /// An action to execute after the main action, with an exception parameter if an error occurred. - /// Determines if the action can be batched. - /// Determines if batching should break on error. - void Add(Action action, Action postAction = null, bool canBatch = true, bool breakBatchOnError = true); + /// The action to execute with the group state. + /// An action to execute after the main action, with an exception parameter if an error occurred. + /// Determines if the action can be batched. + /// Determines if batching should break on error. + void Add(Action action, Action postAction = null, bool canBatch = true, bool breakBatchOnError = true); - /// - /// Adds an action that receives the group state and an additional state parameter. - /// - /// The type of the additional state. - /// The action to execute with the group state and additional state. - /// The additional state for the action. - /// An action to execute after the main action, with an exception parameter if an error occurred. - /// Determines if the action can be batched. - /// Determines if batching should break on error. - /// A function to compare two state values for batching decisions. - void Add(Action action, TState state, Action postAction = null, bool canBatch = true, bool breakBatchOnError = true, Func compareStates = null); - } + /// + /// Adds an action that receives the group state and an additional state parameter. + /// + /// The type of the additional state. + /// The action to execute with the group state and additional state. + /// The additional state for the action. + /// An action to execute after the main action, with an exception parameter if an error occurred. + /// Determines if the action can be batched. + /// Determines if batching should break on error. + /// A function to compare two state values for batching decisions. + void Add(Action action, TState state, Action postAction = null, bool canBatch = true, bool breakBatchOnError = true, Func compareStates = null); + } - private interface IInternalGroup - { - IDisposable Init(); - IGroupItem[] GetItemsAndClear(); - } + private interface IInternalGroup + { + IDisposable Init(); + IGroupItem[] GetItemsAndClear(); + } - private interface IGroupItem - { - void Do(IDisposable scope); - Action PostAction { get; } - bool CanBatch { get; } - bool BreakBatchOnError { get; } - } + private interface IGroupItem + { + void Do(IDisposable scope); + Action PostAction { get; } + bool CanBatch { get; } + bool BreakBatchOnError { get; } + } - private interface IInternalGroupItem - { - bool Equals(IGroupItem other); - int GetStateHashCode(); - } + private interface IInternalGroupItem + { + bool Equals(IGroupItem other); + int GetStateHashCode(); + } - private class Group(DelayAction parent, Func init) : IGroup, IInternalGroup - where T : IDisposable + private class Group(DelayAction parent, Func init) : IGroup, IInternalGroup + where T : IDisposable + { + private class Dummy : IDisposable { - private class Dummy : IDisposable + void IDisposable.Dispose() { - void IDisposable.Dispose() - { - } } + } - private class Item : IGroupItem, IInternalGroupItem - { - private readonly TState _state; - private readonly Func _compareStates; - private readonly Action _action; - - public virtual void Do(IDisposable scope) - { - _action((T)scope, _state); - } + private class Item : IGroupItem, IInternalGroupItem + { + private readonly TState _state; + private readonly Func _compareStates; + private readonly Action _action; - public Action PostAction { get; protected set; } - public bool CanBatch { get; protected set; } - public bool BreakBatchOnError { get; protected set; } + public virtual void Do(IDisposable scope) + { + _action((T)scope, _state); + } - protected Item() - { - } + public Action PostAction { get; protected set; } + public bool CanBatch { get; protected set; } + public bool BreakBatchOnError { get; protected set; } - public Item(Action action, TState state, Action postAction, bool canBatch, bool breakBatchOnError, Func compareStates) - { - _state = state; - _compareStates = compareStates; - _action = action; + protected Item() + { + } - PostAction = postAction; - CanBatch = canBatch; - BreakBatchOnError = breakBatchOnError; - } + public Item(Action action, TState state, Action postAction, bool canBatch, bool breakBatchOnError, Func compareStates) + { + _state = state; + _compareStates = compareStates; + _action = action; - bool IInternalGroupItem.Equals(IGroupItem other) - { - if (_compareStates is null) - return false; + PostAction = postAction; + CanBatch = canBatch; + BreakBatchOnError = breakBatchOnError; + } + bool IInternalGroupItem.Equals(IGroupItem other) + { + if (_compareStates is null) + return false; - if (other is not Item item) - return false; - return _compareStates(_state, item._state); - } + if (other is not Item item) + return false; - int IInternalGroupItem.GetStateHashCode() - { - return typeof(TState).GetHashCode() ^ (_state is null ? 0 : 1) ^ (_compareStates is null ? 0 : 1); - } + return _compareStates(_state, item._state); } - private class FlushItem : Item + int IInternalGroupItem.GetStateHashCode() { - private readonly SyncObject _syncObject = new(); - private bool _isProcessed; - private Exception _err; - - /// - /// Initializes a new instance of the FlushItem class. - /// - /// The parent DelayAction. - /// Indicates if the parent should be disposed after flushing. - public FlushItem(DelayAction parent, bool dispose) - { - if (parent is null) - throw new ArgumentNullException(nameof(parent)); - - PostAction = err => - { - _err = err; + return typeof(TState).GetHashCode() ^ (_state is null ? 0 : 1) ^ (_compareStates is null ? 0 : 1); + } + } - if (dispose) - parent.Dispose(); + private class FlushItem : Item + { + private readonly SyncObject _syncObject = new(); + private bool _isProcessed; + private Exception _err; - lock (_syncObject) - { - _isProcessed = true; - _syncObject.Pulse(); - } - }; - CanBatch = true; - BreakBatchOnError = true; - } + /// + /// Initializes a new instance of the FlushItem class. + /// + /// The parent DelayAction. + /// Indicates if the parent should be disposed after flushing. + public FlushItem(DelayAction parent, bool dispose) + { + if (parent is null) + throw new ArgumentNullException(nameof(parent)); - public override void Do(IDisposable scope) + PostAction = err => { - } + _err = err; + + if (dispose) + parent.Dispose(); - /// - /// Waits for this flush item to be processed. - /// - public void Wait() - { lock (_syncObject) { - if (!_isProcessed) - _syncObject.Wait(); + _isProcessed = true; + _syncObject.Pulse(); } - - if (_err != null) - throw _err; - } + }; + CanBatch = true; + BreakBatchOnError = true; } - private readonly DelayAction _parent = parent ?? throw new ArgumentNullException(nameof(parent)); - private readonly Func _init = init; - private readonly SynchronizedList _actions = []; - private readonly Dummy _dummy = new(); - - public IDisposable Init() + public override void Do(IDisposable scope) { - if (_init is null) - return _dummy; - - var state = _init.Invoke(); - - if (state is null) - throw new InvalidOperationException(); - - return state; } - public void Add(Action action, Action postAction = null, bool canBatch = true, bool breakBatchOnError = true) + /// + /// Waits for this flush item to be processed. + /// + public void Wait() { - if (action is null) - throw new ArgumentNullException(nameof(action)); + lock (_syncObject) + { + if (!_isProcessed) + _syncObject.Wait(); + } - Add((IDisposable s) => action(), postAction, canBatch, breakBatchOnError); + if (_err != null) + throw _err; } + } - public void Add(Action action, Action postAction = null, bool canBatch = true, bool breakBatchOnError = true) - { - if (action is null) - throw new ArgumentNullException(nameof(action)); + private readonly DelayAction _parent = parent ?? throw new ArgumentNullException(nameof(parent)); + private readonly Func _init = init; + private readonly SynchronizedList _actions = []; + private readonly Dummy _dummy = new(); - Add((T scope) => action(scope), postAction, canBatch, breakBatchOnError); - } + public IDisposable Init() + { + if (_init is null) + return _dummy; - public void Add(Action action, Action postAction = null, bool canBatch = true, bool breakBatchOnError = true) - { - if (action is null) - throw new ArgumentNullException(nameof(action)); + var state = _init.Invoke(); - Add((scope, state) => action(scope), null, postAction, canBatch, breakBatchOnError); - } + if (state is null) + throw new InvalidOperationException(); - public void Add(Action action, TState state, Action postAction = null, bool canBatch = true, bool breakBatchOnError = true, Func compareStates = null) - { - Add(new Item(action, state, postAction, canBatch, breakBatchOnError, compareStates)); - } + return state; + } - private void Add(Item item) - { - if (item is null) - throw new ArgumentNullException(nameof(item)); + public void Add(Action action, Action postAction = null, bool canBatch = true, bool breakBatchOnError = true) + { + if (action is null) + throw new ArgumentNullException(nameof(action)); - lock (_actions.SyncRoot) - _actions.Add(item); + Add((IDisposable s) => action(), postAction, canBatch, breakBatchOnError); + } - _parent.TryCreateTimer(); - } + public void Add(Action action, Action postAction = null, bool canBatch = true, bool breakBatchOnError = true) + { + if (action is null) + throw new ArgumentNullException(nameof(action)); - public void WaitFlush(bool dispose) - { - var item = new FlushItem(_parent, dispose); - Add(item); - item.Wait(); - } + Add((T scope) => action(scope), postAction, canBatch, breakBatchOnError); + } - public IGroupItem[] GetItemsAndClear() - => _actions.CopyAndClear(); + public void Add(Action action, Action postAction = null, bool canBatch = true, bool breakBatchOnError = true) + { + if (action is null) + throw new ArgumentNullException(nameof(action)); + + Add((scope, state) => action(scope), null, postAction, canBatch, breakBatchOnError); } - private readonly Action _errorHandler; + public void Add(Action action, TState state, Action postAction = null, bool canBatch = true, bool breakBatchOnError = true, Func compareStates = null) + { + Add(new Item(action, state, postAction, canBatch, breakBatchOnError, compareStates)); + } - private Timer _flushTimer; - private bool _isFlushing; + private void Add(Item item) + { + if (item is null) + throw new ArgumentNullException(nameof(item)); - private readonly CachedSynchronizedList _groups = []; + lock (_actions.SyncRoot) + _actions.Add(item); - /// - /// Initializes a new instance of the DelayAction class. - /// - /// A delegate to handle errors that occur during execution. - public DelayAction(Action errorHandler) + _parent.TryCreateTimer(); + } + + public void WaitFlush(bool dispose) { - _errorHandler = errorHandler ?? throw new ArgumentNullException(nameof(errorHandler)); - DefaultGroup = CreateGroup(null); + var item = new FlushItem(_parent, dispose); + Add(item); + item.Wait(); } - /// - /// Gets the default group for delayed actions. - /// - public IGroup DefaultGroup { get; } + public IGroupItem[] GetItemsAndClear() + => _actions.CopyAndClear(); + } - private TimeSpan _flushInterval = TimeSpan.FromSeconds(1); + private readonly Action _errorHandler; - /// - /// Gets or sets the flush interval for batching actions. - /// - public TimeSpan FlushInterval + private Timer _flushTimer; + private bool _isFlushing; + + private readonly CachedSynchronizedList _groups = []; + + /// + /// Initializes a new instance of the DelayAction class. + /// + /// A delegate to handle errors that occur during execution. + public DelayAction(Action errorHandler) + { + _errorHandler = errorHandler ?? throw new ArgumentNullException(nameof(errorHandler)); + DefaultGroup = CreateGroup(null); + } + + /// + /// Gets the default group for delayed actions. + /// + public IGroup DefaultGroup { get; } + + private TimeSpan _flushInterval = TimeSpan.FromSeconds(1); + + /// + /// Gets or sets the flush interval for batching actions. + /// + public TimeSpan FlushInterval + { + get => _flushInterval; + set { - get => _flushInterval; - set - { - _flushInterval = value; + _flushInterval = value; - lock (_groups.SyncRoot) - { - if (_flushTimer is null) - return; + lock (_groups.SyncRoot) + { + if (_flushTimer is null) + return; - _flushTimer.Interval(_flushInterval); - } + _flushTimer.Interval(_flushInterval); } } + } - /// - /// Creates a new group of delayed actions with a specific state. - /// - /// The type of the group state, which implements IDisposable. - /// - /// A function to initialize the group state. - /// If null, a dummy state is used. - /// - /// A new group for handling delayed actions. - public IGroup CreateGroup(Func init) - where T : IDisposable - { - var group = new Group(this, init); - _groups.Add(group); - return group; - } + /// + /// Creates a new group of delayed actions with a specific state. + /// + /// The type of the group state, which implements IDisposable. + /// + /// A function to initialize the group state. + /// If null, a dummy state is used. + /// + /// A new group for handling delayed actions. + public IGroup CreateGroup(Func init) + where T : IDisposable + { + var group = new Group(this, init); + _groups.Add(group); + return group; + } - /// - /// Deletes a previously created group of delayed actions. - /// - /// The group to delete. - public void DeleteGroup(IGroup group) - { - if (group is null) - throw new ArgumentNullException(nameof(group)); + /// + /// Deletes a previously created group of delayed actions. + /// + /// The group to delete. + public void DeleteGroup(IGroup group) + { + if (group is null) + throw new ArgumentNullException(nameof(group)); - if (group == DefaultGroup) - throw new ArgumentException(); + if (group == DefaultGroup) + throw new ArgumentException(); - _groups.Remove(group); - } + _groups.Remove(group); + } - private int _maxBatchSize = 1000; + private int _maxBatchSize = 1000; - /// - /// Gets or sets the maximum number of actions in a single batch. - /// - public int MaxBatchSize + /// + /// Gets or sets the maximum number of actions in a single batch. + /// + public int MaxBatchSize + { + get => _maxBatchSize; + set { - get => _maxBatchSize; - set - { - if (value <= 0) - throw new ArgumentOutOfRangeException(); + if (value <= 0) + throw new ArgumentOutOfRangeException(); - _maxBatchSize = value; - } + _maxBatchSize = value; } + } - private void TryCreateTimer() + private void TryCreateTimer() + { + lock (_groups.SyncRoot) { - lock (_groups.SyncRoot) + if (!_isFlushing && _flushTimer is null) { - if (!_isFlushing && _flushTimer is null) - { - _flushTimer = ThreadingHelper - .TimerInvariant(OnFlush) - .Interval(_flushInterval); - } + _flushTimer = ThreadingHelper + .TimerInvariant(OnFlush) + .Interval(_flushInterval); } } + } - /// - /// Flushes all queued actions by executing them in batches. - /// - public void OnFlush() + /// + /// Flushes all queued actions by executing them in batches. + /// + public void OnFlush() + { + try { - try + IGroup[] groups; + + lock (_groups.SyncRoot) { - IGroup[] groups; + if (_isFlushing) + return; - lock (_groups.SyncRoot) - { - if (_isFlushing) - return; + _isFlushing = true; - _isFlushing = true; + groups = _groups.Cache; + } - groups = _groups.Cache; - } + var hasItems = false; - var hasItems = false; + try + { + Debug.WriteLine($"Groups: {groups.Length}"); - try + foreach (var group in groups) { - Debug.WriteLine($"Groups: {groups.Length}"); - - foreach (var group in groups) - { - if (IsDisposed) - break; + if (IsDisposed) + break; - var items = ((IInternalGroup)group).GetItemsAndClear(); + var items = ((IInternalGroup)group).GetItemsAndClear(); - if (items.Length == 0) - continue; + if (items.Length == 0) + continue; - Debug.WriteLine($"Flushing: {items.Length}"); + Debug.WriteLine($"Flushing: {items.Length}"); - hasItems = true; + hasItems = true; - var list = new List(); + var list = new List(); - foreach (var item in items) + foreach (var item in items) + { + if (!item.CanBatch) { - if (!item.CanBatch) - { - Debug.WriteLine($"!!! Interrupt: {list.Count}"); + Debug.WriteLine($"!!! Interrupt: {list.Count}"); - BatchFlushAndClear(group, list); - list.Clear(); + BatchFlushAndClear(group, list); + list.Clear(); - if (IsDisposed) - break; + if (IsDisposed) + break; - Flush(item); - } - else - list.Add(item); + Flush(item); } - - if (!IsDisposed) - BatchFlushAndClear(group, list); + else + list.Add(item); } + + if (!IsDisposed) + BatchFlushAndClear(group, list); } - finally + } + finally + { + lock (_groups.SyncRoot) { - lock (_groups.SyncRoot) - { - _isFlushing = false; + _isFlushing = false; - if (!hasItems) + if (!hasItems) + { + if (_flushTimer != null) { - if (_flushTimer != null) - { - _flushTimer.Dispose(); - _flushTimer = null; - } + _flushTimer.Dispose(); + _flushTimer = null; } } } } - catch (Exception ex) - { - _errorHandler(ex); - } } - - private void Flush(IGroupItem item) + catch (Exception ex) { - Exception error; + _errorHandler(ex); + } + } - try - { - item.Do(null); - error = null; - } - catch (Exception ex) - { - _errorHandler(ex); - error = ex; - } + private void Flush(IGroupItem item) + { + Exception error; - item.PostAction?.Invoke(error); + try + { + item.Do(null); + error = null; } - - /// - /// Waits until all queued actions are flushed and optionally disposes the DelayAction instance. - /// - /// If set to true, disposes the DelayAction instance after flushing. - public void WaitFlush(bool dispose) + catch (Exception ex) { - _groups.Cache.Where(g => g != DefaultGroup).ForEach(g => g.WaitFlush(false)); - DefaultGroup.WaitFlush(dispose); + _errorHandler(ex); + error = ex; } - private class ItemComparer : IEqualityComparer + item.PostAction?.Invoke(error); + } + + /// + /// Waits until all queued actions are flushed and optionally disposes the DelayAction instance. + /// + /// If set to true, disposes the DelayAction instance after flushing. + public void WaitFlush(bool dispose) + { + _groups.Cache.Where(g => g != DefaultGroup).ForEach(g => g.WaitFlush(false)); + DefaultGroup.WaitFlush(dispose); + } + + private class ItemComparer : IEqualityComparer + { + bool IEqualityComparer.Equals(IGroupItem x, IGroupItem y) { - bool IEqualityComparer.Equals(IGroupItem x, IGroupItem y) - { - return ((IInternalGroupItem)x).Equals(y); - } + return ((IInternalGroupItem)x).Equals(y); + } - int IEqualityComparer.GetHashCode(IGroupItem obj) - { - return ((IInternalGroupItem)obj).GetStateHashCode(); - } + int IEqualityComparer.GetHashCode(IGroupItem obj) + { + return ((IInternalGroupItem)obj).GetStateHashCode(); } + } - private static readonly IEqualityComparer _itemComparer = new ItemComparer(); + private static readonly IEqualityComparer _itemComparer = new ItemComparer(); - private void BatchFlushAndClear(IGroup group, ICollection actions) - { - if (actions.IsEmpty()) - return; + private void BatchFlushAndClear(IGroup group, ICollection actions) + { + if (actions.IsEmpty()) + return; - Debug.WriteLine($"Batch: {actions.Count}"); + Debug.WriteLine($"Batch: {actions.Count}"); - Exception error = null; + Exception error = null; - try - { - using var scope = ((IInternalGroup)group).Init(); + try + { + using var scope = ((IInternalGroup)group).Init(); - foreach (var packet in actions.Distinct(_itemComparer).Batch(MaxBatchSize)) + foreach (var packet in actions.Distinct(_itemComparer).Batch(MaxBatchSize)) + { + using (var batch = BeginBatch(group)) { - using (var batch = BeginBatch(group)) + foreach (var action in packet) { - foreach (var action in packet) - { - if (action.BreakBatchOnError) - action.Do(scope); - else - Flush(action); + if (action.BreakBatchOnError) + action.Do(scope); + else + Flush(action); - if (IsDisposed) - break; - } - - batch.Commit(); + if (IsDisposed) + break; } - if (IsDisposed) - break; + batch.Commit(); } - } - catch (Exception ex) - { - _errorHandler(ex); - error = ex; - } - actions - .Where(a => a.PostAction != null) - .ForEach(a => a.PostAction(error)); + if (IsDisposed) + break; + } } - - private class DummyBatchContext : IBatchContext + catch (Exception ex) { - void IDisposable.Dispose() - { - } - - void IBatchContext.Commit() - { - } + _errorHandler(ex); + error = ex; } - private readonly DummyBatchContext _batchContext = new(); + actions + .Where(a => a.PostAction != null) + .ForEach(a => a.PostAction(error)); + } - /// - /// Begins a new batch for the specified group. - /// - /// The group for which to begin the batch. - /// An IBatchContext representing the batch operation. - protected virtual IBatchContext BeginBatch(IGroup group) + private class DummyBatchContext : IBatchContext + { + void IDisposable.Dispose() { - return _batchContext; } + + void IBatchContext.Commit() + { + } + } + + private readonly DummyBatchContext _batchContext = new(); + + /// + /// Begins a new batch for the specified group. + /// + /// The group for which to begin the batch. + /// An IBatchContext representing the batch operation. + protected virtual IBatchContext BeginBatch(IGroup group) + { + return _batchContext; } } \ No newline at end of file diff --git a/Serialization/IBatchContext.cs b/Serialization/IBatchContext.cs index 22817093..6c50139d 100644 --- a/Serialization/IBatchContext.cs +++ b/Serialization/IBatchContext.cs @@ -1,15 +1,14 @@ -namespace Ecng.Serialization -{ - using System; +namespace Ecng.Serialization; + +using System; +/// +/// Defines a context for batch operations that supports committing transactions and resource disposal. +/// +public interface IBatchContext : IDisposable +{ /// - /// Defines a context for batch operations that supports committing transactions and resource disposal. + /// Commits the current batch of operations. /// - public interface IBatchContext : IDisposable - { - /// - /// Commits the current batch of operations. - /// - void Commit(); - } + void Commit(); } \ No newline at end of file diff --git a/Serialization/IPersistable.cs b/Serialization/IPersistable.cs index e7dacd6b..cd4aff15 100644 --- a/Serialization/IPersistable.cs +++ b/Serialization/IPersistable.cs @@ -1,45 +1,44 @@ -namespace Ecng.Serialization -{ - using System.Threading; - using System.Threading.Tasks; +namespace Ecng.Serialization; + +using System.Threading; +using System.Threading.Tasks; +/// +/// Interface for objects whose state can be persisted synchronously using a SettingsStorage. +/// +public interface IPersistable +{ /// - /// Interface for objects whose state can be persisted synchronously using a SettingsStorage. + /// Loads the state of the object from the specified SettingsStorage. /// - public interface IPersistable - { - /// - /// Loads the state of the object from the specified SettingsStorage. - /// - /// The SettingsStorage instance from which to load the data. - void Load(SettingsStorage storage); + /// The SettingsStorage instance from which to load the data. + void Load(SettingsStorage storage); - /// - /// Saves the state of the object to the specified SettingsStorage. - /// - /// The SettingsStorage instance to which to save the data. - void Save(SettingsStorage storage); - } + /// + /// Saves the state of the object to the specified SettingsStorage. + /// + /// The SettingsStorage instance to which to save the data. + void Save(SettingsStorage storage); +} +/// +/// Interface for objects whose state can be persisted asynchronously using a SettingsStorage. +/// +public interface IAsyncPersistable +{ /// - /// Interface for objects whose state can be persisted asynchronously using a SettingsStorage. + /// Asynchronously loads the state of the object from the specified SettingsStorage. /// - public interface IAsyncPersistable - { - /// - /// Asynchronously loads the state of the object from the specified SettingsStorage. - /// - /// The SettingsStorage instance from which to load the data. - /// A token to monitor for cancellation requests. - /// A task that represents the asynchronous load operation. - Task LoadAsync(SettingsStorage storage, CancellationToken cancellationToken); + /// The SettingsStorage instance from which to load the data. + /// A token to monitor for cancellation requests. + /// A task that represents the asynchronous load operation. + Task LoadAsync(SettingsStorage storage, CancellationToken cancellationToken); - /// - /// Asynchronously saves the state of the object to the specified SettingsStorage. - /// - /// The SettingsStorage instance to which to save the data. - /// A token to monitor for cancellation requests. - /// A task that represents the asynchronous save operation. - Task SaveAsync(SettingsStorage storage, CancellationToken cancellationToken); - } + /// + /// Asynchronously saves the state of the object to the specified SettingsStorage. + /// + /// The SettingsStorage instance to which to save the data. + /// A token to monitor for cancellation requests. + /// A task that represents the asynchronous save operation. + Task SaveAsync(SettingsStorage storage, CancellationToken cancellationToken); } \ No newline at end of file diff --git a/Serialization/IPersistableAdapter.cs b/Serialization/IPersistableAdapter.cs index e1e9386d..801c64cd 100644 --- a/Serialization/IPersistableAdapter.cs +++ b/Serialization/IPersistableAdapter.cs @@ -1,13 +1,12 @@ -namespace Ecng.Serialization +namespace Ecng.Serialization; + +/// +/// Provides an adapter for persisting values. +/// +public interface IPersistableAdapter { /// - /// Provides an adapter for persisting values. + /// Gets or sets the underlying value that is persisted. /// - public interface IPersistableAdapter - { - /// - /// Gets or sets the underlying value that is persisted. - /// - object UnderlyingValue { get; set; } - } + object UnderlyingValue { get; set; } } \ No newline at end of file diff --git a/Serialization/ISerializer.cs b/Serialization/ISerializer.cs index fb194798..725a6e42 100644 --- a/Serialization/ISerializer.cs +++ b/Serialization/ISerializer.cs @@ -1,58 +1,57 @@ -namespace Ecng.Serialization -{ - using System.IO; - using System.Threading; - using System.Threading.Tasks; +namespace Ecng.Serialization; + +using System.IO; +using System.Threading; +using System.Threading.Tasks; +/// +/// Provides methods for serializing and deserializing objects. +/// +public interface ISerializer +{ /// - /// Provides methods for serializing and deserializing objects. + /// Gets the file extension associated with the serialized format. /// - public interface ISerializer - { - /// - /// Gets the file extension associated with the serialized format. - /// - string FileExtension { get; } + string FileExtension { get; } - /// - /// Asynchronously serializes the specified object graph into the provided stream. - /// - /// The object graph to serialize. - /// The stream to which the object is serialized. - /// A token to monitor for cancellation requests. - /// A representing the asynchronous operation. - public abstract ValueTask SerializeAsync(object graph, Stream stream, CancellationToken cancellationToken); + /// + /// Asynchronously serializes the specified object graph into the provided stream. + /// + /// The object graph to serialize. + /// The stream to which the object is serialized. + /// A token to monitor for cancellation requests. + /// A representing the asynchronous operation. + public abstract ValueTask SerializeAsync(object graph, Stream stream, CancellationToken cancellationToken); - /// - /// Asynchronously deserializes an object graph from the provided stream. - /// - /// The stream from which the object is deserialized. - /// A token to monitor for cancellation requests. - /// A representing the asynchronous operation and the deserialized object. - public abstract ValueTask DeserializeAsync(Stream stream, CancellationToken cancellationToken); - } + /// + /// Asynchronously deserializes an object graph from the provided stream. + /// + /// The stream from which the object is deserialized. + /// A token to monitor for cancellation requests. + /// A representing the asynchronous operation and the deserialized object. + public abstract ValueTask DeserializeAsync(Stream stream, CancellationToken cancellationToken); +} +/// +/// Provides methods for serializing and deserializing objects of type . +/// +/// The type of the object graph to serialize and deserialize. +public interface ISerializer : ISerializer +{ /// - /// Provides methods for serializing and deserializing objects of type . + /// Asynchronously serializes the specified object of type into the provided stream. /// - /// The type of the object graph to serialize and deserialize. - public interface ISerializer : ISerializer - { - /// - /// Asynchronously serializes the specified object of type into the provided stream. - /// - /// The object to serialize. - /// The stream to which the object is serialized. - /// A token to monitor for cancellation requests. - /// A representing the asynchronous operation. - public abstract ValueTask SerializeAsync(T graph, Stream stream, CancellationToken cancellationToken); + /// The object to serialize. + /// The stream to which the object is serialized. + /// A token to monitor for cancellation requests. + /// A representing the asynchronous operation. + public abstract ValueTask SerializeAsync(T graph, Stream stream, CancellationToken cancellationToken); - /// - /// Asynchronously deserializes an object of type from the provided stream. - /// - /// The stream from which the object is deserialized. - /// A token to monitor for cancellation requests. - /// A representing the asynchronous operation and the deserialized object. - public new abstract ValueTask DeserializeAsync(Stream stream, CancellationToken cancellationToken); - } + /// + /// Asynchronously deserializes an object of type from the provided stream. + /// + /// The stream from which the object is deserialized. + /// A token to monitor for cancellation requests. + /// A representing the asynchronous operation and the deserialized object. + public new abstract ValueTask DeserializeAsync(Stream stream, CancellationToken cancellationToken); } \ No newline at end of file diff --git a/Serialization/ISerializerExtensions.cs b/Serialization/ISerializerExtensions.cs index 4c0c52e6..9a09119b 100644 --- a/Serialization/ISerializerExtensions.cs +++ b/Serialization/ISerializerExtensions.cs @@ -1,101 +1,100 @@ -namespace Ecng.Serialization -{ - using System.IO; +namespace Ecng.Serialization; + +using System.IO; - using Ecng.Common; +using Ecng.Common; +/// +/// Provides extension methods for the ISerializer and ISerializer<T> interfaces to support serialization +/// and deserialization operations with files, streams, and byte arrays. +/// +public static class ISerializerExtensions +{ /// - /// Provides extension methods for the ISerializer and ISerializer<T> interfaces to support serialization - /// and deserialization operations with files, streams, and byte arrays. + /// Serializes the specified object graph and writes the output to a file. /// - public static class ISerializerExtensions - { - /// - /// Serializes the specified object graph and writes the output to a file. - /// - /// The serializer instance. - /// The object graph to serialize. - /// The path of the file to write to. - public static void Serialize(this ISerializer serializer, object graph, string fileName) - => File.WriteAllBytes(fileName, serializer.CheckOnNull(nameof(serializer)).Serialize(graph)); + /// The serializer instance. + /// The object graph to serialize. + /// The path of the file to write to. + public static void Serialize(this ISerializer serializer, object graph, string fileName) + => File.WriteAllBytes(fileName, serializer.CheckOnNull(nameof(serializer)).Serialize(graph)); - /// - /// Serializes the specified object graph and returns the result as a byte array. - /// - /// The serializer instance. - /// The object graph to serialize. - /// A byte array containing the serialized data. - public static byte[] Serialize(this ISerializer serializer, object graph) - { - var stream = new MemoryStream(); - serializer.CheckOnNull(nameof(serializer)).Serialize(graph, stream); - return stream.To(); - } - - /// - /// Serializes the specified object graph and writes the output to the provided stream. - /// - /// The serializer instance. - /// The object graph to serialize. - /// The stream to which to write the serialized data. - public static void Serialize(this ISerializer serializer, object graph, Stream stream) - => AsyncHelper.Run(() => serializer.CheckOnNull(nameof(serializer)).SerializeAsync(graph, stream, default)); + /// + /// Serializes the specified object graph and returns the result as a byte array. + /// + /// The serializer instance. + /// The object graph to serialize. + /// A byte array containing the serialized data. + public static byte[] Serialize(this ISerializer serializer, object graph) + { + var stream = new MemoryStream(); + serializer.CheckOnNull(nameof(serializer)).Serialize(graph, stream); + return stream.To(); + } - /// - /// Deserializes the data from the specified file into an object of type T. - /// - /// The type of object to deserialize. - /// The serializer instance. - /// The path of the file to read from. - /// An object of type T. - public static T Deserialize(this ISerializer serializer, string fileName) - { - using var stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read); - return serializer.CheckOnNull(nameof(serializer)).Deserialize(stream); - } + /// + /// Serializes the specified object graph and writes the output to the provided stream. + /// + /// The serializer instance. + /// The object graph to serialize. + /// The stream to which to write the serialized data. + public static void Serialize(this ISerializer serializer, object graph, Stream stream) + => AsyncHelper.Run(() => serializer.CheckOnNull(nameof(serializer)).SerializeAsync(graph, stream, default)); - /// - /// Deserializes the specified byte array into an object of type T. - /// - /// The type of object to deserialize. - /// The serializer instance. - /// The byte array containing the serialized data. - /// An object of type T. - public static T Deserialize(this ISerializer serializer, byte[] data) - { - var stream = new MemoryStream(data); - return serializer.CheckOnNull(nameof(serializer)).Deserialize(stream); - } + /// + /// Deserializes the data from the specified file into an object of type T. + /// + /// The type of object to deserialize. + /// The serializer instance. + /// The path of the file to read from. + /// An object of type T. + public static T Deserialize(this ISerializer serializer, string fileName) + { + using var stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read); + return serializer.CheckOnNull(nameof(serializer)).Deserialize(stream); + } - /// - /// Deserializes the data from the given stream into an object of type T. - /// - /// The type of object to deserialize. - /// The serializer instance. - /// The stream containing the serialized data. - /// An object of type T. - public static T Deserialize(this ISerializer serializer, Stream stream) - => AsyncHelper.Run(() => serializer.CheckOnNull(nameof(serializer)).DeserializeAsync(stream, default)); + /// + /// Deserializes the specified byte array into an object of type T. + /// + /// The type of object to deserialize. + /// The serializer instance. + /// The byte array containing the serialized data. + /// An object of type T. + public static T Deserialize(this ISerializer serializer, byte[] data) + { + var stream = new MemoryStream(data); + return serializer.CheckOnNull(nameof(serializer)).Deserialize(stream); + } - /// - /// Deserializes the specified byte array into an object. - /// - /// The serializer instance. - /// The byte array containing the serialized data. - /// The deserialized object. - public static object Deserialize(this ISerializer serializer, byte[] data) - { - var stream = new MemoryStream(data); - return serializer.CheckOnNull(nameof(serializer)).Deserialize(stream); - } + /// + /// Deserializes the data from the given stream into an object of type T. + /// + /// The type of object to deserialize. + /// The serializer instance. + /// The stream containing the serialized data. + /// An object of type T. + public static T Deserialize(this ISerializer serializer, Stream stream) + => AsyncHelper.Run(() => serializer.CheckOnNull(nameof(serializer)).DeserializeAsync(stream, default)); - /// - /// Deserializes the data from the given stream into an object. - /// - /// The serializer instance. - /// The stream containing the serialized data. - /// The deserialized object. - public static object Deserialize(this ISerializer serializer, Stream stream) - => AsyncHelper.Run(() => serializer.CheckOnNull(nameof(serializer)).DeserializeAsync(stream, default)); + /// + /// Deserializes the specified byte array into an object. + /// + /// The serializer instance. + /// The byte array containing the serialized data. + /// The deserialized object. + public static object Deserialize(this ISerializer serializer, byte[] data) + { + var stream = new MemoryStream(data); + return serializer.CheckOnNull(nameof(serializer)).Deserialize(stream); } + + /// + /// Deserializes the data from the given stream into an object. + /// + /// The serializer instance. + /// The stream containing the serialized data. + /// The deserialized object. + public static object Deserialize(this ISerializer serializer, Stream stream) + => AsyncHelper.Run(() => serializer.CheckOnNull(nameof(serializer)).DeserializeAsync(stream, default)); } \ No newline at end of file diff --git a/Serialization/JsonBoolConverter.cs b/Serialization/JsonBoolConverter.cs index bea64cbf..1e03e063 100644 --- a/Serialization/JsonBoolConverter.cs +++ b/Serialization/JsonBoolConverter.cs @@ -1,39 +1,38 @@ -namespace Ecng.Serialization -{ - using System; +namespace Ecng.Serialization; + +using System; - using Newtonsoft.Json; +using Newtonsoft.Json; +/// +/// Converts boolean values to and from JSON numeric representations (1 or 0). +/// +public class JsonBoolConverter : JsonConverter +{ /// - /// Converts boolean values to and from JSON numeric representations (1 or 0). + /// Writes a boolean value as 1 (true) or 0 (false) to the JSON output. /// - public class JsonBoolConverter : JsonConverter - { - /// - /// Writes a boolean value as 1 (true) or 0 (false) to the JSON output. - /// - /// The to write to. - /// The boolean value to be converted and written. - /// The calling serializer. - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - => writer.WriteValue((bool)value ? 1 : 0); + /// The to write to. + /// The boolean value to be converted and written. + /// The calling serializer. + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + => writer.WriteValue((bool)value ? 1 : 0); - /// - /// Reads a JSON value and converts it to a boolean value where "1" represents true. - /// - /// The to read from. - /// The type of the object to convert to. - /// The existing value of the object being read. - /// The calling serializer. - /// A boolean value where "1" evaluates to true, and any other value to false. - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - => reader.Value.ToString() == "1"; + /// + /// Reads a JSON value and converts it to a boolean value where "1" represents true. + /// + /// The to read from. + /// The type of the object to convert to. + /// The existing value of the object being read. + /// The calling serializer. + /// A boolean value where "1" evaluates to true, and any other value to false. + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + => reader.Value.ToString() == "1"; - /// - /// Determines whether this converter can convert the specified object type. - /// - /// The type of object to check. - /// true if the object type is ; otherwise, false. - public override bool CanConvert(Type objectType) => objectType == typeof(bool); - } + /// + /// Determines whether this converter can convert the specified object type. + /// + /// The type of object to check. + /// true if the object type is ; otherwise, false. + public override bool CanConvert(Type objectType) => objectType == typeof(bool); } \ No newline at end of file diff --git a/Serialization/JsonDateTimeConverter.cs b/Serialization/JsonDateTimeConverter.cs index 4daad846..4bc9bcb6 100644 --- a/Serialization/JsonDateTimeConverter.cs +++ b/Serialization/JsonDateTimeConverter.cs @@ -1,149 +1,148 @@ -namespace Ecng.Serialization +namespace Ecng.Serialization; + +using System; + +using Ecng.Common; + +using Newtonsoft.Json; + +/// +/// Provides a JSON converter for converting DateTime values from Unix timestamps. +/// +/// Determines whether the Unix timestamp is in seconds (true) or another resolution (false). +public class JsonDateTimeConverter(bool isSeconds) : JsonConverter { - using System; + private readonly bool _isSeconds = isSeconds; - using Ecng.Common; + /// + /// Initializes a new instance of the class using seconds as the default resolution. + /// + public JsonDateTimeConverter() + : this(true) + { + } - using Newtonsoft.Json; + /// + /// Determines whether this converter can convert the specified object type. + /// + /// The type of the object to check. + /// true if the objectType is DateTime; otherwise, false. + public override bool CanConvert(Type objectType) + { + return typeof(DateTime) == objectType; + } /// - /// Provides a JSON converter for converting DateTime values from Unix timestamps. + /// Reads the JSON representation of the object and converts it to a . /// - /// Determines whether the Unix timestamp is in seconds (true) or another resolution (false). - public class JsonDateTimeConverter(bool isSeconds) : JsonConverter + /// The to read from. + /// The type of the object to convert. + /// The existing value of the object being read. + /// The calling serializer. + /// The converted value or null if conversion is not possible. + /// Thrown when an error occurs during conversion. + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { - private readonly bool _isSeconds = isSeconds; + //if (reader.TokenType != JsonToken.String) + // throw new JsonReaderException("Unexcepted token '{0}'.".Put(reader.TokenType)); - /// - /// Initializes a new instance of the class using seconds as the default resolution. - /// - public JsonDateTimeConverter() - : this(true) + try { - } + var value = reader.Value.To(); - /// - /// Determines whether this converter can convert the specified object type. - /// - /// The type of the object to check. - /// true if the objectType is DateTime; otherwise, false. - public override bool CanConvert(Type objectType) - { - return typeof(DateTime) == objectType; - } + if (value is not double d || (int)d == 0) + return null; - /// - /// Reads the JSON representation of the object and converts it to a . - /// - /// The to read from. - /// The type of the object to convert. - /// The existing value of the object being read. - /// The calling serializer. - /// The converted value or null if conversion is not possible. - /// Thrown when an error occurs during conversion. - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - //if (reader.TokenType != JsonToken.String) - // throw new JsonReaderException("Unexcepted token '{0}'.".Put(reader.TokenType)); - - try - { - var value = reader.Value.To(); - - if (value is not double d || (int)d == 0) - return null; - - return Convert(d); - } - catch (Exception ex) - { - throw new JsonReaderException(ex.Message, ex); - } + return Convert(d); } - - /// - /// Converts the specified double value to a . - /// - /// The double value representing the Unix timestamp. - /// A corresponding to the Unix timestamp. - protected virtual DateTime Convert(double value) + catch (Exception ex) { - return value.FromUnix(_isSeconds); + throw new JsonReaderException(ex.Message, ex); } + } - /// - /// Writes the JSON representation of the object. - /// This method is not supported. - /// - /// The to write to. - /// The value to convert. - /// The calling serializer. - /// Always thrown as this converter does not support writing. - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - throw new NotSupportedException(); - } + /// + /// Converts the specified double value to a . + /// + /// The double value representing the Unix timestamp. + /// A corresponding to the Unix timestamp. + protected virtual DateTime Convert(double value) + { + return value.FromUnix(_isSeconds); } /// - /// Provides a JSON converter for converting DateTime values from Unix timestamps in milliseconds. + /// Writes the JSON representation of the object. + /// This method is not supported. /// - public class JsonDateTimeMlsConverter : JsonDateTimeConverter + /// The to write to. + /// The value to convert. + /// The calling serializer. + /// Always thrown as this converter does not support writing. + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { - /// - /// Initializes a new instance of the class using milliseconds. - /// - public JsonDateTimeMlsConverter() - : base(false) - { - } + throw new NotSupportedException(); } +} +/// +/// Provides a JSON converter for converting DateTime values from Unix timestamps in milliseconds. +/// +public class JsonDateTimeMlsConverter : JsonDateTimeConverter +{ /// - /// Provides a JSON converter for converting DateTime values from Unix timestamps in microseconds. + /// Initializes a new instance of the class using milliseconds. /// - public class JsonDateTimeMcsConverter : JsonDateTimeConverter + public JsonDateTimeMlsConverter() + : base(false) { - /// - /// Initializes a new instance of the class using microseconds. - /// - public JsonDateTimeMcsConverter() - : base(false) - { - } + } +} - /// - /// Converts the specified double value to a using microsecond precision. - /// - /// The double value representing the Unix timestamp in microseconds. - /// A corresponding to the Unix timestamp. - protected override DateTime Convert(double value) - { - return value.FromUnixMcs(); - } +/// +/// Provides a JSON converter for converting DateTime values from Unix timestamps in microseconds. +/// +public class JsonDateTimeMcsConverter : JsonDateTimeConverter +{ + /// + /// Initializes a new instance of the class using microseconds. + /// + public JsonDateTimeMcsConverter() + : base(false) + { } /// - /// Provides a JSON converter for converting DateTime values from Unix timestamps in nanoseconds. + /// Converts the specified double value to a using microsecond precision. /// - public class JsonDateTimeNanoConverter : JsonDateTimeConverter + /// The double value representing the Unix timestamp in microseconds. + /// A corresponding to the Unix timestamp. + protected override DateTime Convert(double value) { - /// - /// Initializes a new instance of the class using nanoseconds. - /// - public JsonDateTimeNanoConverter() - : base(false) - { - } + return value.FromUnixMcs(); + } +} - /// - /// Converts the specified double value to a using nanosecond precision. - /// - /// The double value representing the Unix timestamp in nanoseconds. - /// A corresponding to the Unix timestamp. - protected override DateTime Convert(double value) - { - return TimeHelper.GregorianStart.AddNanoseconds((long)value); - } +/// +/// Provides a JSON converter for converting DateTime values from Unix timestamps in nanoseconds. +/// +public class JsonDateTimeNanoConverter : JsonDateTimeConverter +{ + /// + /// Initializes a new instance of the class using nanoseconds. + /// + public JsonDateTimeNanoConverter() + : base(false) + { + } + + /// + /// Converts the specified double value to a using nanosecond precision. + /// + /// The double value representing the Unix timestamp in nanoseconds. + /// A corresponding to the Unix timestamp. + protected override DateTime Convert(double value) + { + return TimeHelper.GregorianStart.AddNanoseconds((long)value); } } \ No newline at end of file diff --git a/Serialization/JsonHelper.cs b/Serialization/JsonHelper.cs index d2ed888f..4d6fb192 100644 --- a/Serialization/JsonHelper.cs +++ b/Serialization/JsonHelper.cs @@ -1,197 +1,196 @@ -namespace Ecng.Serialization +namespace Ecng.Serialization; + +using System; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Diagnostics; +using System.Linq; + +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Serialization; + +using Ecng.Common; + +/// +/// Provides helper methods for JSON serialization and deserialization. +/// +public static class JsonHelper { - using System; - using System.Text; - using System.Threading; - using System.Threading.Tasks; - using System.Diagnostics; - using System.Linq; + /// + /// Provides a UTF-8 encoding without a byte order mark (BOM). + /// + public static readonly Encoding UTF8NoBom = new UTF8Encoding(false); - using Newtonsoft.Json; - using Newtonsoft.Json.Linq; - using Newtonsoft.Json.Serialization; + /// + /// Checks that the current token of the JSON reader matches the expected token. + /// + /// The JSON reader. + /// The expected JSON token. + [Conditional("DEBUG")] + public static void ChechExpectedToken(this JsonReader reader, JsonToken token) + { + if (reader.TokenType != token) + throw new InvalidOperationException($"{reader.TokenType} != {token}"); + } - using Ecng.Common; + /// + /// Asynchronously reads the next token from the JSON reader and checks for end-of-file. + /// + /// The JSON reader. + /// A cancellation token. + /// A task that represents the asynchronous read operation. + public static async Task ReadWithCheckAsync(this JsonReader reader, CancellationToken cancellationToken) + { + if (!await reader.ReadAsync(cancellationToken)) + throw new InvalidOperationException("EOF"); + } /// - /// Provides helper methods for JSON serialization and deserialization. + /// Deserializes the JSON string into an object of type T. /// - public static class JsonHelper + /// The target type. + /// The JSON string. + /// The deserialized object. + public static T DeserializeObject(this string content) { - /// - /// Provides a UTF-8 encoding without a byte order mark (BOM). - /// - public static readonly Encoding UTF8NoBom = new UTF8Encoding(false); - - /// - /// Checks that the current token of the JSON reader matches the expected token. - /// - /// The JSON reader. - /// The expected JSON token. - [Conditional("DEBUG")] - public static void ChechExpectedToken(this JsonReader reader, JsonToken token) - { - if (reader.TokenType != token) - throw new InvalidOperationException($"{reader.TokenType} != {token}"); - } + return (T)content.DeserializeObject(typeof(T)); + } - /// - /// Asynchronously reads the next token from the JSON reader and checks for end-of-file. - /// - /// The JSON reader. - /// A cancellation token. - /// A task that represents the asynchronous read operation. - public static async Task ReadWithCheckAsync(this JsonReader reader, CancellationToken cancellationToken) - { - if (!await reader.ReadAsync(cancellationToken)) - throw new InvalidOperationException("EOF"); - } + /// + /// Deserializes the JSON token into an object of type T. + /// + /// The target type. + /// The JSON token. + /// The deserialized object. + public static T DeserializeObject(this JToken token) + { + return (T)token.DeserializeObject(typeof(T)); + } + + /// + /// Deserializes the JSON string into an object of the specified type. + /// + /// The JSON string. + /// The target type. + /// The deserialized object. + public static object DeserializeObject(this string content, Type type) + { + if (type is null) + throw new ArgumentNullException(nameof(type)); - /// - /// Deserializes the JSON string into an object of type T. - /// - /// The target type. - /// The JSON string. - /// The deserialized object. - public static T DeserializeObject(this string content) + if (content.IsEmpty()) { - return (T)content.DeserializeObject(typeof(T)); + if (type.IsClass || type.IsNullable() || type.IsInterface) + return null; + + throw new ArgumentNullException(nameof(content), $"Can't null for {type}."); } - /// - /// Deserializes the JSON token into an object of type T. - /// - /// The target type. - /// The JSON token. - /// The deserialized object. - public static T DeserializeObject(this JToken token) + try { - return (T)token.DeserializeObject(typeof(T)); - } + if (content == "null") + return null; - /// - /// Deserializes the JSON string into an object of the specified type. - /// - /// The JSON string. - /// The target type. - /// The deserialized object. - public static object DeserializeObject(this string content, Type type) + return content.FromJson(type); + } + catch (Exception ex) { - if (type is null) - throw new ArgumentNullException(nameof(type)); + throw new InvalidOperationException($"Can't convert {content} to type '{type.Name}'.", ex); + } + } - if (content.IsEmpty()) - { - if (type.IsClass || type.IsNullable() || type.IsInterface) - return null; + /// + /// Deserializes the JSON string into an object using Newtonsoft.Json. + /// + /// The JSON string. + /// The target type. + /// The deserialized object. + public static object FromJson(this string json, Type type) + => JsonConvert.DeserializeObject(json, type); - throw new ArgumentNullException(nameof(content), $"Can't null for {type}."); - } + /// + /// Deserializes the JSON token into an object of the specified type. + /// + /// The JSON token. + /// The target type. + /// The deserialized object. + public static object DeserializeObject(this JToken token, Type type) + { + if (type is null) + throw new ArgumentNullException(nameof(type)); - try - { - if (content == "null") - return null; + if (token is null) + { + if (type.IsClass || type.IsNullable() || type.IsInterface) + return null; - return content.FromJson(type); - } - catch (Exception ex) - { - throw new InvalidOperationException($"Can't convert {content} to type '{type.Name}'.", ex); - } + throw new ArgumentNullException(nameof(token), $"Can't null for {type}."); } - /// - /// Deserializes the JSON string into an object using Newtonsoft.Json. - /// - /// The JSON string. - /// The target type. - /// The deserialized object. - public static object FromJson(this string json, Type type) - => JsonConvert.DeserializeObject(json, type); - - /// - /// Deserializes the JSON token into an object of the specified type. - /// - /// The JSON token. - /// The target type. - /// The deserialized object. - public static object DeserializeObject(this JToken token, Type type) + try { - if (type is null) - throw new ArgumentNullException(nameof(type)); - - if (token is null) - { - if (type.IsClass || type.IsNullable() || type.IsInterface) - return null; + if (token.Type == JTokenType.String && (string)token == "null") + return null; - throw new ArgumentNullException(nameof(token), $"Can't null for {type}."); - } + return token.ToObject(type); + } + catch (Exception ex) + { + throw new InvalidOperationException($"Can't convert {token} to type '{type.Name}'.", ex); + } + } - try - { - if (token.Type == JTokenType.String && (string)token == "null") - return null; + /// + /// Serializes the object into a JSON string with optional indentation. + /// + /// The object to serialize. + /// True to format the JSON string; otherwise, false. + /// The JSON string. + public static string ToJson(this object obj, bool indent = true) + => ToJson(obj, indent, null); - return token.ToObject(type); - } - catch (Exception ex) - { - throw new InvalidOperationException($"Can't convert {token} to type '{type.Name}'.", ex); - } - } + /// + /// Serializes the object into a JSON string with optional indentation and custom serializer settings. + /// + /// The object to serialize. + /// True to format the JSON string; otherwise, false. + /// The JSON serializer settings. + /// The JSON string. + public static string ToJson(this object obj, bool indent, JsonSerializerSettings settings) + => JsonConvert.SerializeObject(obj, indent ? Formatting.Indented : Formatting.None, settings); - /// - /// Serializes the object into a JSON string with optional indentation. - /// - /// The object to serialize. - /// True to format the JSON string; otherwise, false. - /// The JSON string. - public static string ToJson(this object obj, bool indent = true) - => ToJson(obj, indent, null); - - /// - /// Serializes the object into a JSON string with optional indentation and custom serializer settings. - /// - /// The object to serialize. - /// True to format the JSON string; otherwise, false. - /// The JSON serializer settings. - /// The JSON string. - public static string ToJson(this object obj, bool indent, JsonSerializerSettings settings) - => JsonConvert.SerializeObject(obj, indent ? Formatting.Indented : Formatting.None, settings); - - /// - /// Creates and configures a new instance of JsonSerializerSettings. - /// - /// The configured JsonSerializerSettings instance. - public static JsonSerializerSettings CreateJsonSerializerSettings() + /// + /// Creates and configures a new instance of JsonSerializerSettings. + /// + /// The configured JsonSerializerSettings instance. + public static JsonSerializerSettings CreateJsonSerializerSettings() + { + return new() { - return new() + FloatParseHandling = FloatParseHandling.Decimal, + NullValueHandling = NullValueHandling.Ignore, + ContractResolver = new DefaultContractResolver { - FloatParseHandling = FloatParseHandling.Decimal, - NullValueHandling = NullValueHandling.Ignore, - ContractResolver = new DefaultContractResolver - { - NamingStrategy = new SnakeCaseNamingStrategy() - } - }; - } + NamingStrategy = new SnakeCaseNamingStrategy() + } + }; + } - /// - /// Skips the Byte Order Mark (BOM) from the beginning of a byte array if present. - /// - /// The byte array. - /// The byte array without the BOM. - public static byte[] SkipBom(this byte[] array) - { - if (array is null) - throw new ArgumentNullException(nameof(array)); + /// + /// Skips the Byte Order Mark (BOM) from the beginning of a byte array if present. + /// + /// The byte array. + /// The byte array without the BOM. + public static byte[] SkipBom(this byte[] array) + { + if (array is null) + throw new ArgumentNullException(nameof(array)); - if (array.Length >= 3 && array[0] == 239 && array[1] == 187 && array[2] == 191) - array = [.. array.Skip(3)]; + if (array.Length >= 3 && array[0] == 239 && array[1] == 187 && array[2] == 191) + array = [.. array.Skip(3)]; - return array; - } + return array; } } \ No newline at end of file diff --git a/Serialization/JsonSerializer.cs b/Serialization/JsonSerializer.cs index 261132e9..d2826dce 100644 --- a/Serialization/JsonSerializer.cs +++ b/Serialization/JsonSerializer.cs @@ -1,239 +1,212 @@ -namespace Ecng.Serialization +namespace Ecng.Serialization; + +using System; +using System.Text; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using System.Security; +using System.Linq; + +using Ecng.Common; +using Ecng.Reflection; + +using Newtonsoft.Json; + +/// +/// Contains custom JSON conversion logic. +/// +static class JsonConversions { - using System; - using System.Text; - using System.Collections; - using System.Collections.Generic; - using System.IO; - using System.Threading; - using System.Threading.Tasks; - using System.Security; - using System.Linq; + static JsonConversions() + { + Converter.AddTypedConverter(val => SecureStringEncryptor.Instance.Decrypt([.. val.Select(i => i.To())])); + } +} - using Ecng.Common; - using Ecng.Reflection; +/// +/// Provides JSON serialization settings. +/// +public interface IJsonSerializer +{ + /// + /// Gets or sets a value indicating whether the JSON output should be indented. + /// + bool Indent { get; set; } - using Newtonsoft.Json; + /// + /// Gets or sets the text encoding used for serialization. + /// + Encoding Encoding { get; set; } /// - /// Contains custom JSON conversion logic. + /// Gets or sets a value indicating whether the fill mode is enabled. /// - static class JsonConversions - { - static JsonConversions() - { - Converter.AddTypedConverter(val => SecureStringEncryptor.Instance.Decrypt([.. val.Select(i => i.To())])); - } - } + bool FillMode { get; set; } /// - /// Provides JSON serialization settings. + /// Gets or sets a value indicating whether enums should be serialized as strings. /// - public interface IJsonSerializer - { - /// - /// Gets or sets a value indicating whether the JSON output should be indented. - /// - bool Indent { get; set; } - - /// - /// Gets or sets the text encoding used for serialization. - /// - Encoding Encoding { get; set; } - - /// - /// Gets or sets a value indicating whether the fill mode is enabled. - /// - bool FillMode { get; set; } - - /// - /// Gets or sets a value indicating whether enums should be serialized as strings. - /// - bool EnumAsString { get; set; } - - /// - /// Gets or sets a value indicating whether encrypted values are handled as byte arrays. - /// - bool EncryptedAsByteArray { get; set; } - - /// - /// Gets or sets the buffer size used during serialization and deserialization. - /// - int BufferSize { get; set; } - - /// - /// Gets or sets the null value handling option for JSON serialization. - /// - NullValueHandling NullValueHandling { get; set; } - } + bool EnumAsString { get; set; } + + /// + /// Gets or sets a value indicating whether encrypted values are handled as byte arrays. + /// + bool EncryptedAsByteArray { get; set; } + + /// + /// Gets or sets the buffer size used during serialization and deserialization. + /// + int BufferSize { get; set; } /// - /// Provides JSON serialization and deserialization for a given type. + /// Gets or sets the null value handling option for JSON serialization. /// - /// The type of the graph to serialize and deserialize. - public class JsonSerializer : Serializer, IJsonSerializer + NullValueHandling NullValueHandling { get; set; } +} + +/// +/// Provides JSON serialization and deserialization for a given type. +/// +/// The type of the graph to serialize and deserialize. +public class JsonSerializer : Serializer, IJsonSerializer +{ + static JsonSerializer() { - static JsonSerializer() - { - typeof(JsonConversions).EnsureRunClass(); - } + typeof(JsonConversions).EnsureRunClass(); + } - /// - /// Gets or sets a value indicating whether the JSON output should be indented. - /// - public bool Indent { get; set; } - - /// - /// Gets or sets the text encoding used for serialization. Defaults to UTF8. - /// - public Encoding Encoding { get; set; } = Encoding.UTF8; - - /// - /// Gets or sets a value indicating whether the fill mode is enabled. - /// - public bool FillMode { get; set; } = true; - - /// - /// Gets or sets a value indicating whether enums should be serialized as strings. - /// - public bool EnumAsString { get; set; } - - /// - /// Gets or sets a value indicating whether encrypted values are handled as byte arrays. - /// - public bool EncryptedAsByteArray { get; set; } - - /// - /// Gets or sets the buffer size used during serialization and deserialization. - /// - public int BufferSize { get; set; } = FileSizes.KB; - - /// - /// Gets or sets the null value handling option for JSON serialization. - /// - public NullValueHandling NullValueHandling { get; set; } = NullValueHandling.Include; - - /// - /// Gets the file extension associated with the JSON serializer. - /// - public override string FileExtension => "json"; - - /// - /// Creates a default instance of the JSON serializer with preconfigured settings. - /// - /// A default instance. - public static JsonSerializer CreateDefault() - => new() - { - Indent = true, - EnumAsString = true, - NullValueHandling = NullValueHandling.Ignore, - }; - - private static bool IsJsonPrimitive() => typeof(T).IsSerializablePrimitive() && typeof(T) != typeof(byte[]); - - /// - /// Asynchronously serializes the specified object graph to the provided stream as JSON. - /// - /// The object graph to serialize. - /// The stream to which the graph is serialized. - /// A token that can be used to cancel the serialization operation. - /// A task representing the asynchronous serialization operation. - public override async ValueTask SerializeAsync(T graph, Stream stream, CancellationToken cancellationToken) - { - var isPrimitive = IsJsonPrimitive(); + /// + /// Gets or sets a value indicating whether the JSON output should be indented. + /// + public bool Indent { get; set; } - using var writer = new JsonTextWriter(new StreamWriter(stream, Encoding, BufferSize, true)) - { - Formatting = Indent ? Formatting.Indented : Formatting.None - }; + /// + /// Gets or sets the text encoding used for serialization. Defaults to UTF8. + /// + public Encoding Encoding { get; set; } = Encoding.UTF8; - if (isPrimitive) - await writer.WriteStartArrayAsync(cancellationToken); + /// + /// Gets or sets a value indicating whether the fill mode is enabled. + /// + public bool FillMode { get; set; } = true; - await WriteAsync(writer, graph, cancellationToken); + /// + /// Gets or sets a value indicating whether enums should be serialized as strings. + /// + public bool EnumAsString { get; set; } - if (isPrimitive) - await writer.WriteEndArrayAsync(cancellationToken); - } + /// + /// Gets or sets a value indicating whether encrypted values are handled as byte arrays. + /// + public bool EncryptedAsByteArray { get; set; } - /// - /// Asynchronously deserializes an object graph from the provided JSON stream. - /// - /// The stream from which the object graph is deserialized. - /// A token that can be used to cancel the deserialization operation. - /// A task representing the asynchronous deserialization operation. The task result contains the deserialized object graph. - public override async ValueTask DeserializeAsync(Stream stream, CancellationToken cancellationToken) - { - var isPrimitive = IsJsonPrimitive(); + /// + /// Gets or sets the buffer size used during serialization and deserialization. + /// + public int BufferSize { get; set; } = FileSizes.KB; - using var reader = new JsonTextReader(new StreamReader(stream, Encoding, true, BufferSize, true)) - { - FloatParseHandling = FloatParseHandling.Decimal - }; + /// + /// Gets or sets the null value handling option for JSON serialization. + /// + public NullValueHandling NullValueHandling { get; set; } = NullValueHandling.Include; - if (isPrimitive) - { - if (!await reader.ReadAsync(cancellationToken)) - return default; - } + /// + /// Gets the file extension associated with the JSON serializer. + /// + public override string FileExtension => "json"; - var retVal = (T)await ReadAsync(reader, typeof(T), cancellationToken); + /// + /// Creates a default instance of the JSON serializer with preconfigured settings. + /// + /// A default instance. + public static JsonSerializer CreateDefault() + => new() + { + Indent = true, + EnumAsString = true, + NullValueHandling = NullValueHandling.Ignore, + }; - if (isPrimitive) - await reader.ReadAsync(cancellationToken); + private static bool IsJsonPrimitive() => typeof(T).IsSerializablePrimitive() && typeof(T) != typeof(byte[]); - return retVal; - } + /// + /// Asynchronously serializes the specified object graph to the provided stream as JSON. + /// + /// The object graph to serialize. + /// The stream to which the graph is serialized. + /// A token that can be used to cancel the serialization operation. + /// A task representing the asynchronous serialization operation. + public override async ValueTask SerializeAsync(T graph, Stream stream, CancellationToken cancellationToken) + { + var isPrimitive = IsJsonPrimitive(); - private async ValueTask ReadAsync(JsonReader reader, Type type, CancellationToken cancellationToken) + using var writer = new JsonTextWriter(new StreamWriter(stream, Encoding, BufferSize, true)) { - if (type.IsPersistable()) - { - if (FillMode) - { - var storage = (SettingsStorage)await ReadAsync(reader, typeof(SettingsStorage), cancellationToken); + Formatting = Indent ? Formatting.Indented : Formatting.None + }; - if (storage is null) - return null; + if (isPrimitive) + await writer.WriteStartArrayAsync(cancellationToken); - var per = type.CreateInstance(); + await WriteAsync(writer, graph, cancellationToken); - if (per is IAsyncPersistable asyncPer) - await asyncPer.LoadAsync(storage, default); - else - ((IPersistable)per).Load(storage); + if (isPrimitive) + await writer.WriteEndArrayAsync(cancellationToken); + } - return per; - } - else - { - await reader.ReadWithCheckAsync(cancellationToken); + /// + /// Asynchronously deserializes an object graph from the provided JSON stream. + /// + /// The stream from which the object graph is deserialized. + /// A token that can be used to cancel the deserialization operation. + /// A task representing the asynchronous deserialization operation. The task result contains the deserialized object graph. + public override async ValueTask DeserializeAsync(Stream stream, CancellationToken cancellationToken) + { + var isPrimitive = IsJsonPrimitive(); - if (reader.TokenType == JsonToken.EndArray || reader.TokenType == JsonToken.Null) - return null; + using var reader = new JsonTextReader(new StreamReader(stream, Encoding, true, BufferSize, true)) + { + FloatParseHandling = FloatParseHandling.Decimal + }; - reader.ChechExpectedToken(JsonToken.StartObject); + if (isPrimitive) + { + if (!await reader.ReadAsync(cancellationToken)) + return default; + } - var per = type.CreateInstance(); + var retVal = (T)await ReadAsync(reader, typeof(T), cancellationToken); - var storage = new SettingsStorage(reader, GetValueFromReaderAsync); + if (isPrimitive) + await reader.ReadAsync(cancellationToken); - if (per is IAsyncPersistable asyncPer) - await asyncPer.LoadAsync(storage, default); - else - ((IPersistable)per).Load(storage); + return retVal; + } - await TryClearDeepLevel(reader, storage, cancellationToken); + private async ValueTask ReadAsync(JsonReader reader, Type type, CancellationToken cancellationToken) + { + if (type.IsPersistable()) + { + if (FillMode) + { + var storage = (SettingsStorage)await ReadAsync(reader, typeof(SettingsStorage), cancellationToken); - await reader.ReadWithCheckAsync(cancellationToken); + if (storage is null) + return null; - reader.ChechExpectedToken(JsonToken.EndObject); + var per = type.CreateInstance(); - return per; - } + if (per is IAsyncPersistable asyncPer) + await asyncPer.LoadAsync(storage, default); + else + ((IPersistable)per).Load(storage); + + return per; } - else if (type == typeof(SettingsStorage)) + else { await reader.ReadWithCheckAsync(cancellationToken); @@ -242,241 +215,267 @@ private async ValueTask ReadAsync(JsonReader reader, Type type, Cancella reader.ChechExpectedToken(JsonToken.StartObject); - var storage = new SettingsStorage(); - await FillAsync(storage, reader, cancellationToken); + var per = type.CreateInstance(); - //await reader.ReadWithCheckAsync(cancellationToken); + var storage = new SettingsStorage(reader, GetValueFromReaderAsync); - reader.ChechExpectedToken(JsonToken.EndObject); + if (per is IAsyncPersistable asyncPer) + await asyncPer.LoadAsync(storage, default); + else + ((IPersistable)per).Load(storage); + + await TryClearDeepLevel(reader, storage, cancellationToken); - return storage; - } - else if (type.Is() && type != typeof(string)) - { await reader.ReadWithCheckAsync(cancellationToken); - if (reader.TokenType == JsonToken.EndArray || reader.TokenType == JsonToken.Null) - return null; + reader.ChechExpectedToken(JsonToken.EndObject); - reader.ChechExpectedToken(JsonToken.StartArray); + return per; + } + } + else if (type == typeof(SettingsStorage)) + { + await reader.ReadWithCheckAsync(cancellationToken); - var itemType = type.GetItemType(); + if (reader.TokenType == JsonToken.EndArray || reader.TokenType == JsonToken.Null) + return null; - var col = new List(); + reader.ChechExpectedToken(JsonToken.StartObject); - while (true) - { - var item = await ReadAsync(reader, itemType, cancellationToken); + var storage = new SettingsStorage(); + await FillAsync(storage, reader, cancellationToken); - if (item is null && reader.TokenType == JsonToken.EndArray) - break; + //await reader.ReadWithCheckAsync(cancellationToken); - col.Add(item); - } + reader.ChechExpectedToken(JsonToken.EndObject); + + return storage; + } + else if (type.Is() && type != typeof(string)) + { + await reader.ReadWithCheckAsync(cancellationToken); - reader.ChechExpectedToken(JsonToken.EndArray); + if (reader.TokenType == JsonToken.EndArray || reader.TokenType == JsonToken.Null) + return null; - if (!type.IsArray && type != typeof(IEnumerable<>).Make(itemType)) - return col; + reader.ChechExpectedToken(JsonToken.StartArray); - var arr = Array.CreateInstance(itemType, col.Count); - var idx = 0; + var itemType = type.GetItemType(); - foreach (var item in col) - { - arr.SetValue(item, idx++); - } + var col = new List(); - return arr; - } - else + while (true) { - object value; - - if (type == typeof(DateTime)) - value = await reader.ReadAsDateTimeAsync(cancellationToken); - else if (type == typeof(DateTimeOffset)) - value = await reader.ReadAsDateTimeOffsetAsync(cancellationToken); - else if (type == typeof(byte[])) - value = await reader.ReadAsBytesAsync(cancellationToken); - else if (type == typeof(SecureString)) - { - value = SecureStringEncryptor.Instance.Decrypt(EncryptedAsByteArray - ? await reader.ReadAsBytesAsync(cancellationToken) - : (await reader.ReadAsStringAsync(cancellationToken))?.Base64()); - } - else if (type.TryGetAdapterType(out var adapterType)) - { - value = await ReadAsync(reader, adapterType, cancellationToken); + var item = await ReadAsync(reader, itemType, cancellationToken); - if (value is IPersistableAdapter adapter) - value = adapter.UnderlyingValue; - } - else - value = await reader.ReadAsStringAsync(cancellationToken); + if (item is null && reader.TokenType == JsonToken.EndArray) + break; - return value?.To(type); + col.Add(item); } - } - - private async ValueTask WriteAsync(JsonWriter writer, object value, CancellationToken cancellationToken) - { - async Task WriteSettingsStorageAsync(SettingsStorage storage) - { - await writer.WriteStartObjectAsync(cancellationToken); - foreach (var pair in storage) - { - if (pair.Value is null && NullValueHandling == NullValueHandling.Ignore) - continue; + reader.ChechExpectedToken(JsonToken.EndArray); - await writer.WritePropertyNameAsync(pair.Key, cancellationToken); - await WriteAsync(writer, pair.Value, cancellationToken); - } + if (!type.IsArray && type != typeof(IEnumerable<>).Make(itemType)) + return col; - await writer.WriteEndObjectAsync(cancellationToken); - } + var arr = Array.CreateInstance(itemType, col.Count); + var idx = 0; - if (value is IPersistable per) - { - await WriteSettingsStorageAsync(per.Save()); - } - else if (value is IAsyncPersistable asyncPer) + foreach (var item in col) { - await WriteSettingsStorageAsync(await asyncPer.SaveAsync(cancellationToken)); + arr.SetValue(item, idx++); } - else if (value is SettingsStorage storage) + + return arr; + } + else + { + object value; + + if (type == typeof(DateTime)) + value = await reader.ReadAsDateTimeAsync(cancellationToken); + else if (type == typeof(DateTimeOffset)) + value = await reader.ReadAsDateTimeOffsetAsync(cancellationToken); + else if (type == typeof(byte[])) + value = await reader.ReadAsBytesAsync(cancellationToken); + else if (type == typeof(SecureString)) { - await WriteSettingsStorageAsync(storage); + value = SecureStringEncryptor.Instance.Decrypt(EncryptedAsByteArray + ? await reader.ReadAsBytesAsync(cancellationToken) + : (await reader.ReadAsStringAsync(cancellationToken))?.Base64()); } - else if (value is IEnumerable primCol && value is not string) + else if (type.TryGetAdapterType(out var adapterType)) { - await writer.WriteStartArrayAsync(cancellationToken); + value = await ReadAsync(reader, adapterType, cancellationToken); - foreach (var item in primCol) - await WriteAsync(writer, item, cancellationToken); - - await writer.WriteEndArrayAsync(cancellationToken); - } - else if (value is SecureString secStr) - { - var encrypted = SecureStringEncryptor.Instance.Encrypt(secStr); - await WriteAsync(writer, EncryptedAsByteArray ? encrypted : encrypted?.Base64(), cancellationToken); + if (value is IPersistableAdapter adapter) + value = adapter.UnderlyingValue; } else + value = await reader.ReadAsStringAsync(cancellationToken); + + return value?.To(type); + } + } + + private async ValueTask WriteAsync(JsonWriter writer, object value, CancellationToken cancellationToken) + { + async Task WriteSettingsStorageAsync(SettingsStorage storage) + { + await writer.WriteStartObjectAsync(cancellationToken); + + foreach (var pair in storage) { - if (value is TimeZoneInfo tz) - value = tz.To(); - else if (value is Enum && EnumAsString) - value = value.To(); - else if (value is Type t) - value = t.GetTypeAsString(false); - else if (value != null && value.GetType().TryGetAdapterType(out var adapterType)) - { - var adapter = adapterType.CreateInstance(); - adapter.UnderlyingValue = value; - await WriteAsync(writer, adapter, cancellationToken); - return; - } + if (pair.Value is null && NullValueHandling == NullValueHandling.Ignore) + continue; - await writer.WriteValueAsync(value, cancellationToken); + await writer.WritePropertyNameAsync(pair.Key, cancellationToken); + await WriteAsync(writer, pair.Value, cancellationToken); } + + await writer.WriteEndObjectAsync(cancellationToken); } - private async ValueTask FillAsync(SettingsStorage storage, JsonReader reader, CancellationToken cancellationToken) + if (value is IPersistable per) + { + await WriteSettingsStorageAsync(per.Save()); + } + else if (value is IAsyncPersistable asyncPer) { - if (storage is null) - throw new ArgumentNullException(nameof(storage)); + await WriteSettingsStorageAsync(await asyncPer.SaveAsync(cancellationToken)); + } + else if (value is SettingsStorage storage) + { + await WriteSettingsStorageAsync(storage); + } + else if (value is IEnumerable primCol && value is not string) + { + await writer.WriteStartArrayAsync(cancellationToken); - if (reader is null) - throw new ArgumentNullException(nameof(reader)); + foreach (var item in primCol) + await WriteAsync(writer, item, cancellationToken); - while (true) + await writer.WriteEndArrayAsync(cancellationToken); + } + else if (value is SecureString secStr) + { + var encrypted = SecureStringEncryptor.Instance.Encrypt(secStr); + await WriteAsync(writer, EncryptedAsByteArray ? encrypted : encrypted?.Base64(), cancellationToken); + } + else + { + if (value is TimeZoneInfo tz) + value = tz.To(); + else if (value is Enum && EnumAsString) + value = value.To(); + else if (value is Type t) + value = t.GetTypeAsString(false); + else if (value != null && value.GetType().TryGetAdapterType(out var adapterType)) { - await reader.ReadWithCheckAsync(cancellationToken); + var adapter = adapterType.CreateInstance(); + adapter.UnderlyingValue = value; + await WriteAsync(writer, adapter, cancellationToken); + return; + } - if (reader.TokenType == JsonToken.EndObject) - break; + await writer.WriteValueAsync(value, cancellationToken); + } + } - reader.ChechExpectedToken(JsonToken.PropertyName); + private async ValueTask FillAsync(SettingsStorage storage, JsonReader reader, CancellationToken cancellationToken) + { + if (storage is null) + throw new ArgumentNullException(nameof(storage)); - var propName = (string)reader.Value; + if (reader is null) + throw new ArgumentNullException(nameof(reader)); - await reader.ReadWithCheckAsync(cancellationToken); + while (true) + { + await reader.ReadWithCheckAsync(cancellationToken); + + if (reader.TokenType == JsonToken.EndObject) + break; - object value; + reader.ChechExpectedToken(JsonToken.PropertyName); + + var propName = (string)reader.Value; + + await reader.ReadWithCheckAsync(cancellationToken); + + object value; - switch (reader.TokenType) + switch (reader.TokenType) + { + case JsonToken.StartObject: { - case JsonToken.StartObject: - { - var inner = new SettingsStorage(); - await FillAsync(inner, reader, cancellationToken); - value = inner; - break; - } - case JsonToken.StartArray: - { - await reader.ReadWithCheckAsync(cancellationToken); + var inner = new SettingsStorage(); + await FillAsync(inner, reader, cancellationToken); + value = inner; + break; + } + case JsonToken.StartArray: + { + await reader.ReadWithCheckAsync(cancellationToken); - var list = new List(); + var list = new List(); - while (reader.TokenType != JsonToken.EndArray) + while (reader.TokenType != JsonToken.EndArray) + { + switch (reader.TokenType) { - switch (reader.TokenType) + case JsonToken.StartObject: { - case JsonToken.StartObject: - { - var inner = new SettingsStorage(); - await FillAsync(inner, reader, cancellationToken); - list.Add(inner); - break; - } - default: - list.Add(reader.Value); - break; + var inner = new SettingsStorage(); + await FillAsync(inner, reader, cancellationToken); + list.Add(inner); + break; } - - await reader.ReadWithCheckAsync(cancellationToken); + default: + list.Add(reader.Value); + break; } - value = list.ToArray(); - break; + await reader.ReadWithCheckAsync(cancellationToken); } - default: - value = reader.Value; - break; - } - storage.Set(propName, value); + value = list.ToArray(); + break; + } + default: + value = reader.Value; + break; } + + storage.Set(propName, value); } + } - private async ValueTask TryClearDeepLevel(JsonReader reader, SettingsStorage storage, CancellationToken cancellationToken) - { - var lvl = storage.DeepLevel; + private async ValueTask TryClearDeepLevel(JsonReader reader, SettingsStorage storage, CancellationToken cancellationToken) + { + var lvl = storage.DeepLevel; - if (lvl == 0) - return; + if (lvl == 0) + return; - for (var i = 1; i <= lvl; i++) - await reader.ReadWithCheckAsync(cancellationToken); + for (var i = 1; i <= lvl; i++) + await reader.ReadWithCheckAsync(cancellationToken); - storage.DeepLevel = 0; - } + storage.DeepLevel = 0; + } - private async ValueTask GetValueFromReaderAsync(JsonReader reader, SettingsStorage storage, string name, Type type, CancellationToken cancellationToken) - { - await TryClearDeepLevel(reader, storage, cancellationToken); + private async ValueTask GetValueFromReaderAsync(JsonReader reader, SettingsStorage storage, string name, Type type, CancellationToken cancellationToken) + { + await TryClearDeepLevel(reader, storage, cancellationToken); - await reader.ReadWithCheckAsync(cancellationToken); + await reader.ReadWithCheckAsync(cancellationToken); - reader.ChechExpectedToken(JsonToken.PropertyName); + reader.ChechExpectedToken(JsonToken.PropertyName); - if (!((string)reader.Value).EqualsIgnoreCase(name)) - throw new InvalidOperationException($"{reader.Value} != {name}"); + if (!((string)reader.Value).EqualsIgnoreCase(name)) + throw new InvalidOperationException($"{reader.Value} != {name}"); - return await ReadAsync(reader, type, cancellationToken); - } + return await ReadAsync(reader, type, cancellationToken); } } \ No newline at end of file diff --git a/Serialization/PersistableHelper.cs b/Serialization/PersistableHelper.cs index 4be1d641..fb3b6f26 100644 --- a/Serialization/PersistableHelper.cs +++ b/Serialization/PersistableHelper.cs @@ -1,663 +1,662 @@ -namespace Ecng.Serialization +namespace Ecng.Serialization; + +using System; +using System.Threading; +using System.Threading.Tasks; +using System.Reflection; + +using Ecng.Common; +using Ecng.Collections; +using Ecng.Reflection; + +/// +/// Provides helper methods for persisting, cloning, loading and saving objects. +/// +public static class PersistableHelper { - using System; - using System.Threading; - using System.Threading.Tasks; - using System.Reflection; + private static readonly CachedSynchronizedDictionary _adapterTypes = []; + + private static Type ValidateAdapterType(Type adapterType) + { + if (adapterType is null) + throw new ArgumentNullException(nameof(adapterType)); + + if (!adapterType.IsPersistable()) + throw new ArgumentException($"Not {typeof(IPersistable)}.", nameof(adapterType)); + + if (!adapterType.Is()) + throw new ArgumentException($"Not {typeof(IPersistableAdapter)}.", nameof(adapterType)); - using Ecng.Common; - using Ecng.Collections; - using Ecng.Reflection; + return adapterType; + } /// - /// Provides helper methods for persisting, cloning, loading and saving objects. + /// Registers the adapter type for the given type. /// - public static class PersistableHelper - { - private static readonly CachedSynchronizedDictionary _adapterTypes = []; + /// The type for which the adapter is registered. + /// The adapter type to register. + public static void RegisterAdapterType(this Type type, Type adapterType) + => _adapterTypes.Add(type, ValidateAdapterType(adapterType)); - private static Type ValidateAdapterType(Type adapterType) - { - if (adapterType is null) - throw new ArgumentNullException(nameof(adapterType)); + /// + /// Removes the registered adapter type for the given type. + /// + /// The type whose adapter registration is removed. + /// true if the adapter was removed; otherwise, false. + public static bool RemoveAdapterType(this Type type) + => _adapterTypes.Remove(type); - if (!adapterType.IsPersistable()) - throw new ArgumentException($"Not {typeof(IPersistable)}.", nameof(adapterType)); + /// + /// Tries to get the registered adapter type for the given type. + /// + /// The type to check. + /// When this method returns, contains the adapter type if found. + /// true if found; otherwise, false. + public static bool TryGetAdapterType(this Type type, out Type adapterType) + => _adapterTypes.TryGetValue(type, out adapterType); - if (!adapterType.Is()) - throw new ArgumentException($"Not {typeof(IPersistableAdapter)}.", nameof(adapterType)); + /// + /// Determines whether the specified type is persistable. + /// + /// The type to evaluate. + /// true if the type implements IPersistable or IAsyncPersistable; otherwise, false. + public static bool IsPersistable(this Type type) + => type.Is() || type.Is(); - return adapterType; - } + private const string _typeKey = "type"; + private const string _valueKey = "value"; + private const string _settingsKey = "settings"; - /// - /// Registers the adapter type for the given type. - /// - /// The type for which the adapter is registered. - /// The adapter type to register. - public static void RegisterAdapterType(this Type type, Type adapterType) - => _adapterTypes.Add(type, ValidateAdapterType(adapterType)); - - /// - /// Removes the registered adapter type for the given type. - /// - /// The type whose adapter registration is removed. - /// true if the adapter was removed; otherwise, false. - public static bool RemoveAdapterType(this Type type) - => _adapterTypes.Remove(type); - - /// - /// Tries to get the registered adapter type for the given type. - /// - /// The type to check. - /// When this method returns, contains the adapter type if found. - /// true if found; otherwise, false. - public static bool TryGetAdapterType(this Type type, out Type adapterType) - => _adapterTypes.TryGetValue(type, out adapterType); - - /// - /// Determines whether the specified type is persistable. - /// - /// The type to evaluate. - /// true if the type implements IPersistable or IAsyncPersistable; otherwise, false. - public static bool IsPersistable(this Type type) - => type.Is() || type.Is(); - - private const string _typeKey = "type"; - private const string _valueKey = "value"; - private const string _settingsKey = "settings"; - - /// - /// Creates and initializes an object from the specified settings storage. - /// - /// The type of the persistable object. - /// The settings storage used to create the object. - /// The created and initialized object. - public static T LoadEntire(this SettingsStorage storage) - where T : IPersistable - { - if (storage is null) - throw new ArgumentNullException(nameof(storage)); + /// + /// Creates and initializes an object from the specified settings storage. + /// + /// The type of the persistable object. + /// The settings storage used to create the object. + /// The created and initialized object. + public static T LoadEntire(this SettingsStorage storage) + where T : IPersistable + { + if (storage is null) + throw new ArgumentNullException(nameof(storage)); - var instance = storage.GetValue(_typeKey).CreateInstance(); - instance.Load(storage, _settingsKey); - return instance; - } + var instance = storage.GetValue(_typeKey).CreateInstance(); + instance.Load(storage, _settingsKey); + return instance; + } - /// - /// Saves the entire state of the persistable object into a new settings storage. - /// - /// The persistable object to save. - /// A value indicating whether the type name should be assembly qualified. - /// A settings storage containing the saved state. - public static SettingsStorage SaveEntire(this IPersistable persistable, bool isAssemblyQualifiedName) - { - if (persistable is null) - throw new ArgumentNullException(nameof(persistable)); + /// + /// Saves the entire state of the persistable object into a new settings storage. + /// + /// The persistable object to save. + /// A value indicating whether the type name should be assembly qualified. + /// A settings storage containing the saved state. + public static SettingsStorage SaveEntire(this IPersistable persistable, bool isAssemblyQualifiedName) + { + if (persistable is null) + throw new ArgumentNullException(nameof(persistable)); - return new SettingsStorage() - .Set(_typeKey, persistable.GetType().GetTypeAsString(isAssemblyQualifiedName)) - .Set(_settingsKey, persistable.Save()); - } + return new SettingsStorage() + .Set(_typeKey, persistable.GetType().GetTypeAsString(isAssemblyQualifiedName)) + .Set(_settingsKey, persistable.Save()); + } - /// - /// Clones the specified persistable object. - /// - /// The type of the persistable object. - /// The object to clone. - /// A clone of the object. - public static T Clone(this T obj) - where T : IPersistable - { - if (obj.IsNull()) - return default; + /// + /// Clones the specified persistable object. + /// + /// The type of the persistable object. + /// The object to clone. + /// A clone of the object. + public static T Clone(this T obj) + where T : IPersistable + { + if (obj.IsNull()) + return default; - var clone = obj.GetType().CreateInstance(); - clone.Load(obj.Save()); - return clone; - } + var clone = obj.GetType().CreateInstance(); + clone.Load(obj.Save()); + return clone; + } - /// - /// Asynchronously clones the specified asynchronous persistable object. - /// - /// The type of the asynchronous persistable object. - /// The object to clone. - /// A token for cancellation. - /// A ValueTask with the cloned object. - public static async ValueTask CloneAsync(this T obj, CancellationToken cancellationToken = default) - where T : IAsyncPersistable - { - if (obj.IsNull()) - return default; + /// + /// Asynchronously clones the specified asynchronous persistable object. + /// + /// The type of the asynchronous persistable object. + /// The object to clone. + /// A token for cancellation. + /// A ValueTask with the cloned object. + public static async ValueTask CloneAsync(this T obj, CancellationToken cancellationToken = default) + where T : IAsyncPersistable + { + if (obj.IsNull()) + return default; - var clone = obj.GetType().CreateInstance(); - await clone.LoadAsync(await obj.SaveAsync(cancellationToken), cancellationToken); - return clone; - } + var clone = obj.GetType().CreateInstance(); + await clone.LoadAsync(await obj.SaveAsync(cancellationToken), cancellationToken); + return clone; + } - /// - /// Applies the state from the clone to the target persistable object. - /// - /// The type of the persistable object. - /// The target object. - /// The object from which to copy the state. - public static void Apply(this T obj, T clone) - where T : IPersistable - { - obj.Load(clone.Save()); - } + /// + /// Applies the state from the clone to the target persistable object. + /// + /// The type of the persistable object. + /// The target object. + /// The object from which to copy the state. + public static void Apply(this T obj, T clone) + where T : IPersistable + { + obj.Load(clone.Save()); + } - /// - /// Asynchronously applies the state from the clone to the target asynchronous persistable object. - /// - /// The type of the asynchronous persistable object. - /// The target object. - /// The object from which to copy the state. - /// A token for cancellation. - /// A ValueTask representing the asynchronous operation. - public static async ValueTask ApplyAsync(this T obj, T clone, CancellationToken cancellationToken = default) - where T : IAsyncPersistable - { - await obj.LoadAsync(await clone.SaveAsync(cancellationToken), cancellationToken); - } + /// + /// Asynchronously applies the state from the clone to the target asynchronous persistable object. + /// + /// The type of the asynchronous persistable object. + /// The target object. + /// The object from which to copy the state. + /// A token for cancellation. + /// A ValueTask representing the asynchronous operation. + public static async ValueTask ApplyAsync(this T obj, T clone, CancellationToken cancellationToken = default) + where T : IAsyncPersistable + { + await obj.LoadAsync(await clone.SaveAsync(cancellationToken), cancellationToken); + } - /// - /// Asynchronously saves the state of the asynchronous persistable object to a settings storage. - /// - /// The asynchronous persistable object. - /// A token for cancellation. - /// A ValueTask with the settings storage containing the saved state. - public static async ValueTask SaveAsync(this IAsyncPersistable persistable, CancellationToken cancellationToken = default) - { - if (persistable is null) - throw new ArgumentNullException(nameof(persistable)); + /// + /// Asynchronously saves the state of the asynchronous persistable object to a settings storage. + /// + /// The asynchronous persistable object. + /// A token for cancellation. + /// A ValueTask with the settings storage containing the saved state. + public static async ValueTask SaveAsync(this IAsyncPersistable persistable, CancellationToken cancellationToken = default) + { + if (persistable is null) + throw new ArgumentNullException(nameof(persistable)); - var storage = new SettingsStorage(); - await persistable.SaveAsync(storage, cancellationToken); - return storage; - } + var storage = new SettingsStorage(); + await persistable.SaveAsync(storage, cancellationToken); + return storage; + } - /// - /// Asynchronously loads an asynchronous persistable object using the specified type. - /// - /// The settings storage to load from. - /// The type of the asynchronous persistable object. - /// A token for cancellation. - /// A ValueTask with the loaded asynchronous persistable object. - public static async ValueTask LoadAsync(this SettingsStorage storage, Type type, CancellationToken cancellationToken = default) - { - if (storage is null) - throw new ArgumentNullException(nameof(storage)); + /// + /// Asynchronously loads an asynchronous persistable object using the specified type. + /// + /// The settings storage to load from. + /// The type of the asynchronous persistable object. + /// A token for cancellation. + /// A ValueTask with the loaded asynchronous persistable object. + public static async ValueTask LoadAsync(this SettingsStorage storage, Type type, CancellationToken cancellationToken = default) + { + if (storage is null) + throw new ArgumentNullException(nameof(storage)); - var obj = type.CreateInstance(); - await obj.LoadAsync(storage, cancellationToken); - return obj; - } + var obj = type.CreateInstance(); + await obj.LoadAsync(storage, cancellationToken); + return obj; + } - /// - /// Asynchronously loads an asynchronous persistable object of type T. - /// - /// The type of the asynchronous persistable object. - /// The settings storage to load from. - /// A token for cancellation. - /// A ValueTask with the loaded object of type T. - public static async ValueTask LoadAsync(this SettingsStorage storage, CancellationToken cancellationToken = default) - where T : IAsyncPersistable, new() - => (T)await storage.LoadAsync(typeof(T), cancellationToken); - - /// - /// Saves the state of the persistable object to a settings storage. - /// - /// The persistable object to save. - /// A settings storage containing the saved state. - public static SettingsStorage Save(this IPersistable persistable) - { - if (persistable is null) - throw new ArgumentNullException(nameof(persistable)); + /// + /// Asynchronously loads an asynchronous persistable object of type T. + /// + /// The type of the asynchronous persistable object. + /// The settings storage to load from. + /// A token for cancellation. + /// A ValueTask with the loaded object of type T. + public static async ValueTask LoadAsync(this SettingsStorage storage, CancellationToken cancellationToken = default) + where T : IAsyncPersistable, new() + => (T)await storage.LoadAsync(typeof(T), cancellationToken); - var storage = new SettingsStorage(); - persistable.Save(storage); - return storage; - } + /// + /// Saves the state of the persistable object to a settings storage. + /// + /// The persistable object to save. + /// A settings storage containing the saved state. + public static SettingsStorage Save(this IPersistable persistable) + { + if (persistable is null) + throw new ArgumentNullException(nameof(persistable)); - /// - /// Loads an IPersistable object of the specified type from the settings storage. - /// - /// The settings storage to load from. - /// The type of the persistable object. - /// The loaded persistable object. - public static IPersistable Load(this SettingsStorage storage, Type type) - { - if (storage is null) - throw new ArgumentNullException(nameof(storage)); + var storage = new SettingsStorage(); + persistable.Save(storage); + return storage; + } - var obj = type.CreateInstance(); - obj.Load(storage); - return obj; - } + /// + /// Loads an IPersistable object of the specified type from the settings storage. + /// + /// The settings storage to load from. + /// The type of the persistable object. + /// The loaded persistable object. + public static IPersistable Load(this SettingsStorage storage, Type type) + { + if (storage is null) + throw new ArgumentNullException(nameof(storage)); - /// - /// Loads an IPersistable object of type T from the settings storage. - /// - /// The type of the persistable object. - /// The settings storage to load from. - /// The loaded object of type T. - public static T Load(this SettingsStorage storage) - where T : IPersistable - => (T)storage.Load(typeof(T)); - - /// - /// Forces the persistable object to load its state from the given settings storage. - /// - /// The type of the persistable object. - /// The target object. - /// The settings storage to load from. - public static void ForceLoad(this T t, SettingsStorage storage) - where T : IPersistable - => t.Load(storage); - - /// - /// Adds a persistable object's state as a value in the settings storage. - /// - /// The settings storage to update. - /// The name of the setting. - /// The persistable object whose state is added. - public static void SetValue(this SettingsStorage storage, string name, IPersistable persistable) - { - if (storage is null) - throw new ArgumentNullException(nameof(storage)); + var obj = type.CreateInstance(); + obj.Load(storage); + return obj; + } - storage.SetValue(name, persistable.Save()); - } + /// + /// Loads an IPersistable object of type T from the settings storage. + /// + /// The type of the persistable object. + /// The settings storage to load from. + /// The loaded object of type T. + public static T Load(this SettingsStorage storage) + where T : IPersistable + => (T)storage.Load(typeof(T)); - /// - /// - [Obsolete("Use overload with serializer param.")] - public static void LoadFromString(this IPersistable persistable, string value) - where TSerializer : ISerializer, new() - => new TSerializer().LoadFromString(persistable, value); - - /// - /// - [Obsolete("Use overload with serializer param.")] - public static SettingsStorage LoadFromString(this string value) - where TSerializer : ISerializer, new() - => new TSerializer().LoadFromString(value); - - /// - /// Loads the state of the persistable object from a string using the provided serializer. - /// - /// The serializer to use. - /// The persistable object to load. - /// The string representation of the state. - public static void LoadFromString(this ISerializer serializer, IPersistable persistable, string value) - { - if (persistable is null) - throw new ArgumentNullException(nameof(persistable)); + /// + /// Forces the persistable object to load its state from the given settings storage. + /// + /// The type of the persistable object. + /// The target object. + /// The settings storage to load from. + public static void ForceLoad(this T t, SettingsStorage storage) + where T : IPersistable + => t.Load(storage); - persistable.Load(serializer.LoadFromString(value)); - } + /// + /// Adds a persistable object's state as a value in the settings storage. + /// + /// The settings storage to update. + /// The name of the setting. + /// The persistable object whose state is added. + public static void SetValue(this SettingsStorage storage, string name, IPersistable persistable) + { + if (storage is null) + throw new ArgumentNullException(nameof(storage)); - /// - /// Loads a value of type TValue from a string using the provided serializer. - /// - /// The type of the value to load. - /// The serializer to use. - /// The string representation of the value. - /// The deserialized value. - public static TValue LoadFromString(this ISerializer serializer, string value) - { - if (value is null) - throw new ArgumentNullException(nameof(value)); + storage.SetValue(name, persistable.Save()); + } - return Do.Invariant(() => serializer.Deserialize(value.UTF8())); - } + /// + /// + [Obsolete("Use overload with serializer param.")] + public static void LoadFromString(this IPersistable persistable, string value) + where TSerializer : ISerializer, new() + => new TSerializer().LoadFromString(persistable, value); - /// - /// - [Obsolete("Use overload with serializer param.")] - public static string SaveToString(this IPersistable persistable) - where TSerializer : ISerializer, new() - => new TSerializer().SaveToString(persistable); - - /// - /// - [Obsolete("Use overload with serializer param.")] - public static string SaveToString(this SettingsStorage settings) - where TSerializer : ISerializer, new() - => new TSerializer().SaveToString(settings); - - /// - /// Saves the state of the persistable object to a string using the provided serializer. - /// - /// The serializer to use. - /// The persistable object to save. - /// A string representing the saved state. - public static string SaveToString(this ISerializer serializer, IPersistable persistable) - { - if (persistable is null) - throw new ArgumentNullException(nameof(persistable)); + /// + /// + [Obsolete("Use overload with serializer param.")] + public static SettingsStorage LoadFromString(this string value) + where TSerializer : ISerializer, new() + => new TSerializer().LoadFromString(value); - return serializer.SaveToString(persistable.Save()); - } + /// + /// Loads the state of the persistable object from a string using the provided serializer. + /// + /// The serializer to use. + /// The persistable object to load. + /// The string representation of the state. + public static void LoadFromString(this ISerializer serializer, IPersistable persistable, string value) + { + if (persistable is null) + throw new ArgumentNullException(nameof(persistable)); - /// - /// Saves the settings to a string using the provided serializer. - /// - /// The type of the settings. - /// The serializer to use. - /// The settings to save. - /// A string representing the settings. - public static string SaveToString(this ISerializer serializer, TValue settings) - { - if (serializer is null) - throw new ArgumentNullException(nameof(serializer)); + persistable.Load(serializer.LoadFromString(value)); + } - if (settings is null) - throw new ArgumentNullException(nameof(settings)); + /// + /// Loads a value of type TValue from a string using the provided serializer. + /// + /// The type of the value to load. + /// The serializer to use. + /// The string representation of the value. + /// The deserialized value. + public static TValue LoadFromString(this ISerializer serializer, string value) + { + if (value is null) + throw new ArgumentNullException(nameof(value)); - return Do.Invariant(() => serializer.Serialize(settings).UTF8()); - } + return Do.Invariant(() => serializer.Deserialize(value.UTF8())); + } - /// - /// Determines whether the specified type is a serializable primitive. - /// - /// The type to examine. - /// true if the type is a primitive or Uri; otherwise, false. - public static bool IsSerializablePrimitive(this Type type) - => type.IsPrimitive() || type == typeof(Uri); - - /// - /// Converts a tuple implementing IRefTuple to a settings storage. - /// - /// The tuple to convert. - /// A settings storage representing the tuple. - public static SettingsStorage ToStorage(this IRefTuple tuple) - { - if (tuple is null) - throw new ArgumentNullException(nameof(tuple)); + /// + /// + [Obsolete("Use overload with serializer param.")] + public static string SaveToString(this IPersistable persistable) + where TSerializer : ISerializer, new() + => new TSerializer().SaveToString(persistable); - var storage = new SettingsStorage(); - var idx = 0; + /// + /// + [Obsolete("Use overload with serializer param.")] + public static string SaveToString(this SettingsStorage settings) + where TSerializer : ISerializer, new() + => new TSerializer().SaveToString(settings); - foreach (var value in tuple.Values) - { - storage.Set(RefTuple.GetName(idx++), value); - } + /// + /// Saves the state of the persistable object to a string using the provided serializer. + /// + /// The serializer to use. + /// The persistable object to save. + /// A string representing the saved state. + public static string SaveToString(this ISerializer serializer, IPersistable persistable) + { + if (persistable is null) + throw new ArgumentNullException(nameof(persistable)); - return storage; - } + return serializer.SaveToString(persistable.Save()); + } - /// - /// Converts the settings storage to a RefPair of the specified types. - /// - /// The type of the first element. - /// The type of the second element. - /// The settings storage to convert. - /// A RefPair containing the converted values. - public static RefPair ToRefPair(this SettingsStorage storage) - { - if (storage is null) - throw new ArgumentNullException(nameof(storage)); + /// + /// Saves the settings to a string using the provided serializer. + /// + /// The type of the settings. + /// The serializer to use. + /// The settings to save. + /// A string representing the settings. + public static string SaveToString(this ISerializer serializer, TValue settings) + { + if (serializer is null) + throw new ArgumentNullException(nameof(serializer)); - var tuple = new RefPair(); - tuple.First = storage.GetValue(nameof(tuple.First)); - tuple.Second = storage.GetValue(nameof(tuple.Second)); - return tuple; - } + if (settings is null) + throw new ArgumentNullException(nameof(settings)); - /// - /// Converts the settings storage to a RefTriple of the specified types. - /// - /// The type of the first element. - /// The type of the second element. - /// The type of the third element. - /// The settings storage to convert. - /// A RefTriple containing the converted values. - public static RefTriple ToRefTriple(this SettingsStorage storage) - { - if (storage is null) - throw new ArgumentNullException(nameof(storage)); - - var tuple = new RefTriple(); - tuple.First = storage.GetValue(nameof(tuple.First)); - tuple.Second = storage.GetValue(nameof(tuple.Second)); - tuple.Third = storage.GetValue(nameof(tuple.Third)); - return tuple; - } + return Do.Invariant(() => serializer.Serialize(settings).UTF8()); + } - /// - /// Converts the settings storage to a RefQuadruple of the specified types. - /// - /// The type of the first element. - /// The type of the second element. - /// The type of the third element. - /// The type of the fourth element. - /// The settings storage to convert. - /// A RefQuadruple containing the converted values. - public static RefQuadruple ToRefQuadruple(this SettingsStorage storage) - { - if (storage is null) - throw new ArgumentNullException(nameof(storage)); - - var tuple = new RefQuadruple(); - tuple.First = storage.GetValue(nameof(tuple.First)); - tuple.Second = storage.GetValue(nameof(tuple.Second)); - tuple.Third = storage.GetValue(nameof(tuple.Third)); - tuple.Fourth = storage.GetValue(nameof(tuple.Fourth)); - return tuple; - } + /// + /// Determines whether the specified type is a serializable primitive. + /// + /// The type to examine. + /// true if the type is a primitive or Uri; otherwise, false. + public static bool IsSerializablePrimitive(this Type type) + => type.IsPrimitive() || type == typeof(Uri); + + /// + /// Converts a tuple implementing IRefTuple to a settings storage. + /// + /// The tuple to convert. + /// A settings storage representing the tuple. + public static SettingsStorage ToStorage(this IRefTuple tuple) + { + if (tuple is null) + throw new ArgumentNullException(nameof(tuple)); + + var storage = new SettingsStorage(); + var idx = 0; - /// - /// Converts the settings storage to a RefFive tuple of the specified types. - /// - /// The type of the first element. - /// The type of the second element. - /// The type of the third element. - /// The type of the fourth element. - /// The type of the fifth element. - /// The settings storage to convert. - /// A RefFive tuple containing the converted values. - public static RefFive ToRefFive(this SettingsStorage storage) + foreach (var value in tuple.Values) { - if (storage is null) - throw new ArgumentNullException(nameof(storage)); - - var tuple = new RefFive(); - tuple.First = storage.GetValue(nameof(tuple.First)); - tuple.Second = storage.GetValue(nameof(tuple.Second)); - tuple.Third = storage.GetValue(nameof(tuple.Third)); - tuple.Fourth = storage.GetValue(nameof(tuple.Fourth)); - tuple.Fifth = storage.GetValue(nameof(tuple.Fifth)); - return tuple; + storage.Set(RefTuple.GetName(idx++), value); } - /// - /// Converts the settings storage to a MemberInfo. - /// - /// The settings storage to convert. - /// The converted MemberInfo. - public static MemberInfo ToMember(this SettingsStorage storage) - => storage.ToMember(); - - /// - /// Converts the settings storage to a member of type T. - /// - /// The expected type of the member. - /// The settings storage to convert. - /// The converted member of type T. - public static T ToMember(this SettingsStorage storage) - where T : MemberInfo - { - if (storage is null) - throw new ArgumentNullException(nameof(storage)); + return storage; + } - var type = storage.GetValue(_typeKey); - var member = storage.GetValue(_valueKey, storage.GetValue("name", string.Empty)); + /// + /// Converts the settings storage to a RefPair of the specified types. + /// + /// The type of the first element. + /// The type of the second element. + /// The settings storage to convert. + /// A RefPair containing the converted values. + public static RefPair ToRefPair(this SettingsStorage storage) + { + if (storage is null) + throw new ArgumentNullException(nameof(storage)); - return member.IsEmpty() ? type.To() : type.GetMember(member); - } + var tuple = new RefPair(); + tuple.First = storage.GetValue(nameof(tuple.First)); + tuple.Second = storage.GetValue(nameof(tuple.Second)); + return tuple; + } - /// - /// Converts the specified member to a settings storage. - /// - /// The type of the member. - /// The member to convert. - /// A value indicating whether the type name should be assembly qualified. - /// A settings storage representing the member. - public static SettingsStorage ToStorage(this T member, bool isAssemblyQualifiedName = default) - where T : MemberInfo - { - if (member is null) - throw new ArgumentNullException(nameof(member)); + /// + /// Converts the settings storage to a RefTriple of the specified types. + /// + /// The type of the first element. + /// The type of the second element. + /// The type of the third element. + /// The settings storage to convert. + /// A RefTriple containing the converted values. + public static RefTriple ToRefTriple(this SettingsStorage storage) + { + if (storage is null) + throw new ArgumentNullException(nameof(storage)); + + var tuple = new RefTriple(); + tuple.First = storage.GetValue(nameof(tuple.First)); + tuple.Second = storage.GetValue(nameof(tuple.Second)); + tuple.Third = storage.GetValue(nameof(tuple.Third)); + return tuple; + } - var storage = new SettingsStorage(); + /// + /// Converts the settings storage to a RefQuadruple of the specified types. + /// + /// The type of the first element. + /// The type of the second element. + /// The type of the third element. + /// The type of the fourth element. + /// The settings storage to convert. + /// A RefQuadruple containing the converted values. + public static RefQuadruple ToRefQuadruple(this SettingsStorage storage) + { + if (storage is null) + throw new ArgumentNullException(nameof(storage)); + + var tuple = new RefQuadruple(); + tuple.First = storage.GetValue(nameof(tuple.First)); + tuple.Second = storage.GetValue(nameof(tuple.Second)); + tuple.Third = storage.GetValue(nameof(tuple.Third)); + tuple.Fourth = storage.GetValue(nameof(tuple.Fourth)); + return tuple; + } - storage.Set(_typeKey, (member as Type ?? member.ReflectedType).GetTypeAsString(isAssemblyQualifiedName)); + /// + /// Converts the settings storage to a RefFive tuple of the specified types. + /// + /// The type of the first element. + /// The type of the second element. + /// The type of the third element. + /// The type of the fourth element. + /// The type of the fifth element. + /// The settings storage to convert. + /// A RefFive tuple containing the converted values. + public static RefFive ToRefFive(this SettingsStorage storage) + { + if (storage is null) + throw new ArgumentNullException(nameof(storage)); + + var tuple = new RefFive(); + tuple.First = storage.GetValue(nameof(tuple.First)); + tuple.Second = storage.GetValue(nameof(tuple.Second)); + tuple.Third = storage.GetValue(nameof(tuple.Third)); + tuple.Fourth = storage.GetValue(nameof(tuple.Fourth)); + tuple.Fifth = storage.GetValue(nameof(tuple.Fifth)); + return tuple; + } - if (member.ReflectedType != null) - storage.Set(_valueKey, member.Name); - - return storage; - } + /// + /// Converts the settings storage to a MemberInfo. + /// + /// The settings storage to convert. + /// The converted MemberInfo. + public static MemberInfo ToMember(this SettingsStorage storage) + => storage.ToMember(); - /// - /// Loads the state of the persistable object from the specified settings storage using the given key. - /// - /// The persistable object to load. - /// The settings storage. - /// The name of the value within the storage. - public static void Load(this IPersistable persistable, SettingsStorage settings, string name) - { - if (persistable is null) - throw new ArgumentNullException(nameof(persistable)); + /// + /// Converts the settings storage to a member of type T. + /// + /// The expected type of the member. + /// The settings storage to convert. + /// The converted member of type T. + public static T ToMember(this SettingsStorage storage) + where T : MemberInfo + { + if (storage is null) + throw new ArgumentNullException(nameof(storage)); - if (settings is null) - throw new ArgumentNullException(nameof(settings)); + var type = storage.GetValue(_typeKey); + var member = storage.GetValue(_valueKey, storage.GetValue("name", string.Empty)); - persistable.Load(settings.GetValue(name)); - } + return member.IsEmpty() ? type.To() : type.GetMember(member); + } - /// - /// Loads the state of the persistable object from the specified settings storage using the given key if it is not null. - /// - /// The persistable object to load. - /// The settings storage. - /// The name of the value within the storage. - /// true if the state was loaded; otherwise, false. - public static bool LoadIfNotNull(this IPersistable persistable, SettingsStorage settings, string name) - { - if (settings is null) - throw new ArgumentNullException(nameof(settings)); + /// + /// Converts the specified member to a settings storage. + /// + /// The type of the member. + /// The member to convert. + /// A value indicating whether the type name should be assembly qualified. + /// A settings storage representing the member. + public static SettingsStorage ToStorage(this T member, bool isAssemblyQualifiedName = default) + where T : MemberInfo + { + if (member is null) + throw new ArgumentNullException(nameof(member)); - return persistable.LoadIfNotNull(settings.GetValue(name)); - } + var storage = new SettingsStorage(); - /// - /// Loads the state of the persistable object from the specified settings storage if the storage is not null. - /// - /// The persistable object to load. - /// The settings storage. - /// true if the state was loaded; otherwise, false. - public static bool LoadIfNotNull(this IPersistable persistable, SettingsStorage storage) - { - if (persistable is null) - throw new ArgumentNullException(nameof(persistable)); + storage.Set(_typeKey, (member as Type ?? member.ReflectedType).GetTypeAsString(isAssemblyQualifiedName)); - if (storage is null) - return false; + if (member.ReflectedType != null) + storage.Set(_valueKey, member.Name); + + return storage; + } - persistable.Load(storage); - return true; - } + /// + /// Loads the state of the persistable object from the specified settings storage using the given key. + /// + /// The persistable object to load. + /// The settings storage. + /// The name of the value within the storage. + public static void Load(this IPersistable persistable, SettingsStorage settings, string name) + { + if (persistable is null) + throw new ArgumentNullException(nameof(persistable)); - /// - /// Converts an object to a settings storage with type and value. - /// - /// The object to convert. - /// A value indicating whether the type name should be assembly qualified. - /// A settings storage representing the object. - public static SettingsStorage ToStorage(this object value, bool isAssemblyQualifiedName = default) - => new SettingsStorage() - .Set(_typeKey, value.CheckOnNull().GetType().GetTypeAsString(isAssemblyQualifiedName)) - .Set(_valueKey, value.To()) - ; - - /// - /// Converts the settings storage back to an object. - /// - /// The settings storage. - /// The object represented by the settings storage. - public static object FromStorage(this SettingsStorage storage) - { - if (storage is null) - throw new ArgumentNullException(nameof(storage)); + if (settings is null) + throw new ArgumentNullException(nameof(settings)); - var value = storage.GetValue(_valueKey).To(storage.GetValue(_typeKey)); + persistable.Load(settings.GetValue(name)); + } - if (value is DateTime dt) - value = dt.ToUniversalTime(); + /// + /// Loads the state of the persistable object from the specified settings storage using the given key if it is not null. + /// + /// The persistable object to load. + /// The settings storage. + /// The name of the value within the storage. + /// true if the state was loaded; otherwise, false. + public static bool LoadIfNotNull(this IPersistable persistable, SettingsStorage settings, string name) + { + if (settings is null) + throw new ArgumentNullException(nameof(settings)); - return value; - } + return persistable.LoadIfNotNull(settings.GetValue(name)); + } - private static readonly SynchronizedDictionary serialize, Func deserialize)> _customSerializers = new(); + /// + /// Loads the state of the persistable object from the specified settings storage if the storage is not null. + /// + /// The persistable object to load. + /// The settings storage. + /// true if the state was loaded; otherwise, false. + public static bool LoadIfNotNull(this IPersistable persistable, SettingsStorage storage) + { + if (persistable is null) + throw new ArgumentNullException(nameof(persistable)); - /// - /// Registers a custom serializer for a specific type. - /// - /// The type for which to register the serializer. - /// A function to serialize the object to a settings storage. - /// A function to deserialize from a settings storage to the object. - public static void RegisterCustomSerializer(Func serialize, Func deserialize) - { - if (serialize is null) throw new ArgumentNullException(nameof(serialize)); - if (deserialize is null) throw new ArgumentNullException(nameof(deserialize)); + if (storage is null) + return false; - _customSerializers[typeof(T)] = (o => serialize((T)o), s => deserialize(s)); - } + persistable.Load(storage); + return true; + } - /// - /// Unregisters the custom serializer for a specific type. - /// - /// The type for which to unregister the serializer. - public static void UnRegisterCustomSerializer() - => _customSerializers.Remove(typeof(T)); - - /// - /// Tries to serialize an object using a registered custom serializer. - /// - /// The type of the object to serialize. - /// The object to serialize. - /// When this method returns, contains the resulting settings storage if serialization was successful. - /// true if serialization was successful; otherwise, false. - public static bool TrySerialize(this T value, out SettingsStorage storage) - { - storage = default; + /// + /// Converts an object to a settings storage with type and value. + /// + /// The object to convert. + /// A value indicating whether the type name should be assembly qualified. + /// A settings storage representing the object. + public static SettingsStorage ToStorage(this object value, bool isAssemblyQualifiedName = default) + => new SettingsStorage() + .Set(_typeKey, value.CheckOnNull().GetType().GetTypeAsString(isAssemblyQualifiedName)) + .Set(_valueKey, value.To()) + ; - if (!_customSerializers.TryGetValue(typeof(T), out var serializer)) - return false; + /// + /// Converts the settings storage back to an object. + /// + /// The settings storage. + /// The object represented by the settings storage. + public static object FromStorage(this SettingsStorage storage) + { + if (storage is null) + throw new ArgumentNullException(nameof(storage)); - storage = serializer.serialize(value); - return true; - } + var value = storage.GetValue(_valueKey).To(storage.GetValue(_typeKey)); - /// - /// Tries to deserialize a settings storage into an object of type T using a registered custom serializer. - /// - /// The type to deserialize. - /// The settings storage containing serialized data. - /// When this method returns, contains the deserialized object if successful. - /// true if deserialization was successful; otherwise, false. - public static bool TryDeserialize(this SettingsStorage storage, out T value) - { - value = default; + if (value is DateTime dt) + value = dt.ToUniversalTime(); - if (!_customSerializers.TryGetValue(typeof(T), out var serializer)) - return false; + return value; + } - value = (T)serializer.deserialize(storage); - return true; - } + private static readonly SynchronizedDictionary serialize, Func deserialize)> _customSerializers = new(); + + /// + /// Registers a custom serializer for a specific type. + /// + /// The type for which to register the serializer. + /// A function to serialize the object to a settings storage. + /// A function to deserialize from a settings storage to the object. + public static void RegisterCustomSerializer(Func serialize, Func deserialize) + { + if (serialize is null) throw new ArgumentNullException(nameof(serialize)); + if (deserialize is null) throw new ArgumentNullException(nameof(deserialize)); + + _customSerializers[typeof(T)] = (o => serialize((T)o), s => deserialize(s)); + } + + /// + /// Unregisters the custom serializer for a specific type. + /// + /// The type for which to unregister the serializer. + public static void UnRegisterCustomSerializer() + => _customSerializers.Remove(typeof(T)); + + /// + /// Tries to serialize an object using a registered custom serializer. + /// + /// The type of the object to serialize. + /// The object to serialize. + /// When this method returns, contains the resulting settings storage if serialization was successful. + /// true if serialization was successful; otherwise, false. + public static bool TrySerialize(this T value, out SettingsStorage storage) + { + storage = default; + + if (!_customSerializers.TryGetValue(typeof(T), out var serializer)) + return false; + + storage = serializer.serialize(value); + return true; + } + + /// + /// Tries to deserialize a settings storage into an object of type T using a registered custom serializer. + /// + /// The type to deserialize. + /// The settings storage containing serialized data. + /// When this method returns, contains the deserialized object if successful. + /// true if deserialization was successful; otherwise, false. + public static bool TryDeserialize(this SettingsStorage storage, out T value) + { + value = default; + + if (!_customSerializers.TryGetValue(typeof(T), out var serializer)) + return false; + + value = (T)serializer.deserialize(storage); + return true; } } \ No newline at end of file diff --git a/Serialization/SecureStringEncryptor.cs b/Serialization/SecureStringEncryptor.cs index f6925a13..b1cd127f 100644 --- a/Serialization/SecureStringEncryptor.cs +++ b/Serialization/SecureStringEncryptor.cs @@ -1,55 +1,54 @@ -namespace Ecng.Serialization -{ - using System; - using System.Security; - using System.Security.Cryptography; +namespace Ecng.Serialization; - using Ecng.Common; - using Ecng.Security; +using System; +using System.Security; +using System.Security.Cryptography; - class SecureStringEncryptor - { - private static readonly Lazy _instance = new(() => new()); - public static SecureStringEncryptor Instance => _instance.Value; +using Ecng.Common; +using Ecng.Security; - private readonly SecureString _key = "RClVEDn0O3EUsKqym1qd".Secure(); - private readonly byte[] _salt = "3hj67-!3".To(); +class SecureStringEncryptor +{ + private static readonly Lazy _instance = new(() => new()); + public static SecureStringEncryptor Instance => _instance.Value; - public SecureString Key { get; set; } - public byte[] Entropy { get; set; } + private readonly SecureString _key = "RClVEDn0O3EUsKqym1qd".Secure(); + private readonly byte[] _salt = "3hj67-!3".To(); - public SecureString Decrypt(byte[] source) - { - if (source is null) - return null; + public SecureString Key { get; set; } + public byte[] Entropy { get; set; } - try - { - if (Scope.Current?.Value.DoNotEncrypt != true) - source = source.DecryptAes(_key.UnSecure(), _salt, _salt); + public SecureString Decrypt(byte[] source) + { + if (source is null) + return null; - return source.To().Secure(); - } - catch (CryptographicException ex) - { - if (ContinueOnExceptionContext.TryProcess(ex)) - return null; + try + { + if (Scope.Current?.Value.DoNotEncrypt != true) + source = source.DecryptAes(_key.UnSecure(), _salt, _salt); - throw; - } + return source.To().Secure(); } - - public byte[] Encrypt(SecureString instance) + catch (CryptographicException ex) { - if (instance is null) + if (ContinueOnExceptionContext.TryProcess(ex)) return null; - var plainText = instance.UnSecure().To(); + throw; + } + } - if (Scope.Current?.Value.DoNotEncrypt == true) - return plainText; + public byte[] Encrypt(SecureString instance) + { + if (instance is null) + return null; - return plainText.EncryptAes(_key.UnSecure(), _salt, _salt); - } + var plainText = instance.UnSecure().To(); + + if (Scope.Current?.Value.DoNotEncrypt == true) + return plainText; + + return plainText.EncryptAes(_key.UnSecure(), _salt, _salt); } } \ No newline at end of file diff --git a/Serialization/Serializer.cs b/Serialization/Serializer.cs index 80799bfe..b0b1a660 100644 --- a/Serialization/Serializer.cs +++ b/Serialization/Serializer.cs @@ -1,54 +1,53 @@ -namespace Ecng.Serialization -{ - using System.IO; - using System.Threading; - using System.Threading.Tasks; +namespace Ecng.Serialization; + +using System.IO; +using System.Threading; +using System.Threading.Tasks; +/// +/// Provides an abstract base class for type-specific serializers. +/// +/// The type of the graph to serialize and deserialize. +public abstract class Serializer : ISerializer +{ /// - /// Provides an abstract base class for type-specific serializers. + /// Gets the file extension associated with this serializer. /// - /// The type of the graph to serialize and deserialize. - public abstract class Serializer : ISerializer - { - /// - /// Gets the file extension associated with this serializer. - /// - public abstract string FileExtension { get; } + public abstract string FileExtension { get; } - /// - /// Serializes the specified object graph to the provided stream asynchronously. - /// - /// The object graph to serialize. - /// The stream to which the graph is serialized. - /// A token that can be used to cancel the serialization operation. - /// A task representing the asynchronous serialize operation. - public abstract ValueTask SerializeAsync(T graph, Stream stream, CancellationToken cancellationToken); + /// + /// Serializes the specified object graph to the provided stream asynchronously. + /// + /// The object graph to serialize. + /// The stream to which the graph is serialized. + /// A token that can be used to cancel the serialization operation. + /// A task representing the asynchronous serialize operation. + public abstract ValueTask SerializeAsync(T graph, Stream stream, CancellationToken cancellationToken); - /// - /// Deserializes an object graph from the provided stream asynchronously. - /// - /// The stream from which the object graph is deserialized. - /// A token that can be used to cancel the deserialization operation. - /// A task that represents the asynchronous deserialize operation. The task result contains the deserialized object graph. - public abstract ValueTask DeserializeAsync(Stream stream, CancellationToken cancellationToken); + /// + /// Deserializes an object graph from the provided stream asynchronously. + /// + /// The stream from which the object graph is deserialized. + /// A token that can be used to cancel the deserialization operation. + /// A task that represents the asynchronous deserialize operation. The task result contains the deserialized object graph. + public abstract ValueTask DeserializeAsync(Stream stream, CancellationToken cancellationToken); - /// - /// Non-generic implementation of the serialize method. - /// - /// The object graph to serialize. - /// The stream to which the graph is serialized. - /// A token that can be used to cancel the serialization operation. - /// A task representing the asynchronous serialize operation. - ValueTask ISerializer.SerializeAsync(object graph, Stream stream, CancellationToken cancellationToken) - => SerializeAsync((T)graph, stream, cancellationToken); + /// + /// Non-generic implementation of the serialize method. + /// + /// The object graph to serialize. + /// The stream to which the graph is serialized. + /// A token that can be used to cancel the serialization operation. + /// A task representing the asynchronous serialize operation. + ValueTask ISerializer.SerializeAsync(object graph, Stream stream, CancellationToken cancellationToken) + => SerializeAsync((T)graph, stream, cancellationToken); - /// - /// Non-generic implementation of the deserialize method. - /// - /// The stream from which the object graph is deserialized. - /// A token that can be used to cancel the deserialization operation. - /// A task that represents the asynchronous deserialize operation. The task result contains the deserialized object graph. - async ValueTask ISerializer.DeserializeAsync(Stream stream, CancellationToken cancellationToken) - => await DeserializeAsync(stream, cancellationToken); - } + /// + /// Non-generic implementation of the deserialize method. + /// + /// The stream from which the object graph is deserialized. + /// A token that can be used to cancel the deserialization operation. + /// A task that represents the asynchronous deserialize operation. The task result contains the deserialized object graph. + async ValueTask ISerializer.DeserializeAsync(Stream stream, CancellationToken cancellationToken) + => await DeserializeAsync(stream, cancellationToken); } \ No newline at end of file diff --git a/Serialization/SettingsStorage.cs b/Serialization/SettingsStorage.cs index 1eb6651c..ba161cd7 100644 --- a/Serialization/SettingsStorage.cs +++ b/Serialization/SettingsStorage.cs @@ -1,262 +1,261 @@ -namespace Ecng.Serialization +namespace Ecng.Serialization; + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Security; +using System.Threading; +using System.Threading.Tasks; + +using Ecng.Collections; +using Ecng.Common; +using Ecng.Reflection; + +using Newtonsoft.Json; + +using Nito.AsyncEx; + +/// +/// Provides a synchronized storage for application settings with support for XML serialization. +/// +public class SettingsStorage : SynchronizedDictionary { - using System; - using System.Collections; - using System.Collections.Generic; - using System.Linq; - using System.Security; - using System.Threading; - using System.Threading.Tasks; + private readonly JsonReader _reader; + private readonly Func> _readJson; - using Ecng.Collections; - using Ecng.Common; - using Ecng.Reflection; + /// + /// Initializes a new instance of the class. + /// + public SettingsStorage() + : base(StringComparer.InvariantCultureIgnoreCase) + { + } - using Newtonsoft.Json; + /// + /// Initializes a new instance of the class using the specified JSON reader and delegate for reading JSON. + /// + /// The JSON reader used for deserialization. + /// The delegate to read JSON values. + internal SettingsStorage(JsonReader reader, Func> readJson) + : this() + { + _reader = reader ?? throw new ArgumentNullException(nameof(reader)); + _readJson = readJson ?? throw new ArgumentNullException(nameof(readJson)); + } - using Nito.AsyncEx; + /// + /// Gets the names of all stored settings. + /// + public IEnumerable Names => this.SyncGet(d => d.Keys.ToArray()); /// - /// Provides a synchronized storage for application settings with support for XML serialization. + /// Sets the value for a specified setting name. /// - public class SettingsStorage : SynchronizedDictionary + /// The type of the value to set. + /// The name of the setting. + /// The value to set. + /// The current instance of . + public SettingsStorage Set(string name, T value) { - private readonly JsonReader _reader; - private readonly Func> _readJson; - - /// - /// Initializes a new instance of the class. - /// - public SettingsStorage() - : base(StringComparer.InvariantCultureIgnoreCase) - { - } + SetValue(name, value); + return this; + } - /// - /// Initializes a new instance of the class using the specified JSON reader and delegate for reading JSON. - /// - /// The JSON reader used for deserialization. - /// The delegate to read JSON values. - internal SettingsStorage(JsonReader reader, Func> readJson) - : this() - { - _reader = reader ?? throw new ArgumentNullException(nameof(reader)); - _readJson = readJson ?? throw new ArgumentNullException(nameof(readJson)); - } + /// + /// Ensures that the current instance is not used as a reader. + /// + private void EnsureIsNotReader() + { + if (_reader != null) + throw new InvalidOperationException("_reader != null"); + } - /// - /// Gets the names of all stored settings. - /// - public IEnumerable Names => this.SyncGet(d => d.Keys.ToArray()); - - /// - /// Sets the value for a specified setting name. - /// - /// The type of the value to set. - /// The name of the setting. - /// The value to set. - /// The current instance of . - public SettingsStorage Set(string name, T value) - { - SetValue(name, value); - return this; - } + /// + /// Sets the value for a specified setting name. + /// + /// The type of the value being set. + /// The name of the setting. + /// The value to set. + public void SetValue(string name, T value) + { + EnsureIsNotReader(); - /// - /// Ensures that the current instance is not used as a reader. - /// - private void EnsureIsNotReader() - { - if (_reader != null) - throw new InvalidOperationException("_reader != null"); - } + this[name.ThrowIfEmpty(nameof(name))] = value; + } - /// - /// Sets the value for a specified setting name. - /// - /// The type of the value being set. - /// The name of the setting. - /// The value to set. - public void SetValue(string name, T value) - { - EnsureIsNotReader(); + /// + /// Determines whether the storage contains a setting with the specified name. + /// + /// The name of the setting. + /// true if the setting exists; otherwise, false. + public bool Contains(string name) + { + return ContainsKey(name.ThrowIfEmpty(nameof(name))); + } - this[name.ThrowIfEmpty(nameof(name))] = value; - } + /// + /// Determines whether the storage contains a setting with the given key. + /// + /// The key of the setting. + /// true if the key exists; otherwise, false. + public override bool ContainsKey(string key) + { + EnsureIsNotReader(); - /// - /// Determines whether the storage contains a setting with the specified name. - /// - /// The name of the setting. - /// true if the setting exists; otherwise, false. - public bool Contains(string name) + return base.ContainsKey(key); + } + + /// + /// Gets or sets the deep level of deserialization. + /// + internal int DeepLevel { get; set; } + + /// + /// Gets the value of a setting with the specified name and converts it to the specified type. + /// + /// The type of value expected. + /// The name of the setting. + /// The default value if the setting is not found or is null. + /// The setting value converted to type . + public T GetValue(string name, T defaultValue = default) + => (T)GetValue(typeof(T), name, defaultValue); + + /// + /// Gets the value of a setting with the specified name and converts it to the given type. + /// + /// The type of value expected. + /// The name of the setting. + /// The default value if the setting is not found or null. + /// The setting value converted to the specified type. + public object GetValue(Type type, string name, object defaultValue = default) + { + if (type is null) + throw new ArgumentNullException(nameof(type)); + + if (name.IsEmpty()) + throw new ArgumentNullException(nameof(name)); + + if (_reader != null) { - return ContainsKey(name.ThrowIfEmpty(nameof(name))); + var res = AsyncHelper.Run(() => GetValueFromReaderAsync(type, name, default)); + + if (res is null) + return defaultValue; + + return res; } - /// - /// Determines whether the storage contains a setting with the given key. - /// - /// The key of the setting. - /// true if the key exists; otherwise, false. - public override bool ContainsKey(string key) + if (!TryGetValue(name, out var value)) + return defaultValue; + + if (value is SettingsStorage storage) { - EnsureIsNotReader(); + if (typeof(SettingsStorage).Is(type)) + return storage; - return base.ContainsKey(key); - } + var obj = Activator.CreateInstance(type); - /// - /// Gets or sets the deep level of deserialization. - /// - internal int DeepLevel { get; set; } - - /// - /// Gets the value of a setting with the specified name and converts it to the specified type. - /// - /// The type of value expected. - /// The name of the setting. - /// The default value if the setting is not found or is null. - /// The setting value converted to type . - public T GetValue(string name, T defaultValue = default) - => (T)GetValue(typeof(T), name, defaultValue); - - /// - /// Gets the value of a setting with the specified name and converts it to the given type. - /// - /// The type of value expected. - /// The name of the setting. - /// The default value if the setting is not found or null. - /// The setting value converted to the specified type. - public object GetValue(Type type, string name, object defaultValue = default) + if (obj is IAsyncPersistable asyncPer) + AsyncContext.Run(() => asyncPer.LoadAsync(storage, default)); + else if (obj is IPersistable per) + per.Load(storage); + else + throw new ArgumentOutOfRangeException(type.To()); + + return obj; + } + else if (type.IsCollection() && type.GetItemType().IsPersistable()) { - if (type is null) - throw new ArgumentNullException(nameof(type)); + if (value is null) + return default; - if (name.IsEmpty()) - throw new ArgumentNullException(nameof(name)); + var elemType = type.GetItemType(); - if (_reader != null) - { - var res = AsyncHelper.Run(() => GetValueFromReaderAsync(type, name, default)); + var arr = ((IEnumerable)value) + .Cast() + .Select(storage => + { + if (storage is null) + return null; - if (res is null) - return defaultValue; + var per = Activator.CreateInstance(elemType); - return res; - } + if (per is IAsyncPersistable asyncPer) + AsyncContext.Run(() => asyncPer.LoadAsync(storage, default)); + else + ((IPersistable)per).Load(storage); - if (!TryGetValue(name, out var value)) - return defaultValue; + return per; + }) + .ToArray(); - if (value is SettingsStorage storage) - { - if (typeof(SettingsStorage).Is(type)) - return storage; - - var obj = Activator.CreateInstance(type); - - if (obj is IAsyncPersistable asyncPer) - AsyncContext.Run(() => asyncPer.LoadAsync(storage, default)); - else if (obj is IPersistable per) - per.Load(storage); - else - throw new ArgumentOutOfRangeException(type.To()); - - return obj; - } - else if (type.IsCollection() && type.GetItemType().IsPersistable()) - { - if (value is null) - return default; - - var elemType = type.GetItemType(); - - var arr = ((IEnumerable)value) - .Cast() - .Select(storage => - { - if (storage is null) - return null; - - var per = Activator.CreateInstance(elemType); - - if (per is IAsyncPersistable asyncPer) - AsyncContext.Run(() => asyncPer.LoadAsync(storage, default)); - else - ((IPersistable)per).Load(storage); - - return per; - }) - .ToArray(); - - var typedArr = elemType.CreateArray(arr.Length); - arr.CopyTo(typedArr, 0); - return typedArr.To(type); - } - else if (type == typeof(SecureString) && value is string str) - { - value = SecureStringEncryptor.Instance.Decrypt(str.Base64()); - } - - return value.To(type); + var typedArr = elemType.CreateArray(arr.Length); + arr.CopyTo(typedArr, 0); + return typedArr.To(type); } - - /// - /// Gets the value of a setting with the specified name and converts it to the specified type. Returns a default value if not found. - /// - /// The type of the value expected. - /// The name of the setting. - /// The default value if the setting is not found. - /// The setting value converted to type . - public T TryGet(string name, T defaultValue = default) - => (T)TryGet(typeof(T), name, defaultValue); - - /// - /// Gets the value of a setting with the specified name and converts it to the given type. Returns a default value if not found. - /// - /// The expected type of the setting. - /// The name of the setting. - /// The default value if the setting is not found. - /// The setting value converted to the specified type. - public object TryGet(Type type, string name, object defaultValue = default) - => GetValue(type, name, defaultValue); - - /// - /// Asynchronously gets the value of a setting with the specified name and converts it to the specified type. - /// - /// The expected type of the value. - /// The name of the setting. - /// The default value if the setting is not found. - /// The cancellation token. - /// A representing the asynchronous operation producing the value converted to type . - public async ValueTask GetValueAsync(string name, T defaultValue = default, CancellationToken cancellationToken = default) - => (T)await GetValueAsync(typeof(T), name, defaultValue, cancellationToken); - - /// - /// Asynchronously gets the value of a setting with the specified name and converts it to the given type. - /// - /// The expected type of the value. - /// The name of the setting. - /// The default value if the setting is not found. - /// The cancellation token. - /// A representing the asynchronous operation producing the setting value converted to the specified type. - public async ValueTask GetValueAsync(Type type, string name, object defaultValue = default, CancellationToken cancellationToken = default) + else if (type == typeof(SecureString) && value is string str) { - if (_reader is null) - return GetValue(type, name, defaultValue); - else - return await GetValueFromReaderAsync(type, name, cancellationToken) ?? defaultValue; + value = SecureStringEncryptor.Instance.Decrypt(str.Base64()); } - /// - /// Asynchronously gets the value from the JSON reader using the provided delegate. - /// - /// The expected type of the value. - /// The name of the setting. - /// The cancellation token. - /// A representing the asynchronous operation producing the value converted to the specified type. - private async ValueTask GetValueFromReaderAsync(Type type, string name, CancellationToken cancellationToken) - => await _readJson(_reader, this, name, type, cancellationToken); + return value.To(type); + } + + /// + /// Gets the value of a setting with the specified name and converts it to the specified type. Returns a default value if not found. + /// + /// The type of the value expected. + /// The name of the setting. + /// The default value if the setting is not found. + /// The setting value converted to type . + public T TryGet(string name, T defaultValue = default) + => (T)TryGet(typeof(T), name, defaultValue); + + /// + /// Gets the value of a setting with the specified name and converts it to the given type. Returns a default value if not found. + /// + /// The expected type of the setting. + /// The name of the setting. + /// The default value if the setting is not found. + /// The setting value converted to the specified type. + public object TryGet(Type type, string name, object defaultValue = default) + => GetValue(type, name, defaultValue); + + /// + /// Asynchronously gets the value of a setting with the specified name and converts it to the specified type. + /// + /// The expected type of the value. + /// The name of the setting. + /// The default value if the setting is not found. + /// The cancellation token. + /// A representing the asynchronous operation producing the value converted to type . + public async ValueTask GetValueAsync(string name, T defaultValue = default, CancellationToken cancellationToken = default) + => (T)await GetValueAsync(typeof(T), name, defaultValue, cancellationToken); + + /// + /// Asynchronously gets the value of a setting with the specified name and converts it to the given type. + /// + /// The expected type of the value. + /// The name of the setting. + /// The default value if the setting is not found. + /// The cancellation token. + /// A representing the asynchronous operation producing the setting value converted to the specified type. + public async ValueTask GetValueAsync(Type type, string name, object defaultValue = default, CancellationToken cancellationToken = default) + { + if (_reader is null) + return GetValue(type, name, defaultValue); + else + return await GetValueFromReaderAsync(type, name, cancellationToken) ?? defaultValue; } + + /// + /// Asynchronously gets the value from the JSON reader using the provided delegate. + /// + /// The expected type of the value. + /// The name of the setting. + /// The cancellation token. + /// A representing the asynchronous operation producing the value converted to the specified type. + private async ValueTask GetValueFromReaderAsync(Type type, string name, CancellationToken cancellationToken) + => await _readJson(_reader, this, name, type, cancellationToken); } \ No newline at end of file diff --git a/Serialization/TransactionFileStream.cs b/Serialization/TransactionFileStream.cs index 93a309a4..f12a0ba9 100644 --- a/Serialization/TransactionFileStream.cs +++ b/Serialization/TransactionFileStream.cs @@ -1,178 +1,177 @@ -namespace Ecng.Serialization +namespace Ecng.Serialization; + +using System; +using System.Diagnostics; +using System.IO; + +using Ecng.Common; + +/// +/// Represents a transactional file stream that writes data to a temporary file and, upon disposal, commits the changes to the target file. +/// +public class TransactionFileStream : Stream { - using System; - using System.Diagnostics; - using System.IO; + private readonly string _name; + private readonly string _nameTemp; - using Ecng.Common; + private FileStream _temp; /// - /// Represents a transactional file stream that writes data to a temporary file and, upon disposal, commits the changes to the target file. + /// Initializes a new instance of the class. /// - public class TransactionFileStream : Stream + /// The name of the target file. + /// The file mode that specifies the type of operations to be performed on the file. + public TransactionFileStream(string name, FileMode mode) { - private readonly string _name; - private readonly string _nameTemp; + _name = name.ThrowIfEmpty(nameof(name)); + _nameTemp = _name + ".tmp"; - private FileStream _temp; - - /// - /// Initializes a new instance of the class. - /// - /// The name of the target file. - /// The file mode that specifies the type of operations to be performed on the file. - public TransactionFileStream(string name, FileMode mode) + switch (mode) { - _name = name.ThrowIfEmpty(nameof(name)); - _nameTemp = _name + ".tmp"; + case FileMode.CreateNew: + { + if (File.Exists(_name)) + throw new IOException(); - switch (mode) + break; + } + case FileMode.Create: + break; + case FileMode.Open: + { + File.Copy(_name, _nameTemp, true); + break; + } + case FileMode.OpenOrCreate: { - case FileMode.CreateNew: - { - if (File.Exists(_name)) - throw new IOException(); - - break; - } - case FileMode.Create: - break; - case FileMode.Open: - { + if (File.Exists(_name)) File.Copy(_name, _nameTemp, true); - break; - } - case FileMode.OpenOrCreate: - { - if (File.Exists(_name)) - File.Copy(_name, _nameTemp, true); - - break; - } - case FileMode.Truncate: - break; - case FileMode.Append: - { - if (File.Exists(_name)) - File.Copy(_name, _nameTemp, true); - - break; - } - default: - throw new ArgumentOutOfRangeException(nameof(mode), mode, null); + + break; } + case FileMode.Truncate: + break; + case FileMode.Append: + { + if (File.Exists(_name)) + File.Copy(_name, _nameTemp, true); - _temp = new FileStream(_nameTemp, mode, FileAccess.Write); + break; + } + default: + throw new ArgumentOutOfRangeException(nameof(mode), mode, null); } - /// - /// Releases the unmanaged resources used by the and optionally releases the managed resources. - /// Copies the temporary file to the target file and then deletes the temporary file. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected override void Dispose(bool disposing) - { - if (_temp is null) - return; + _temp = new FileStream(_nameTemp, mode, FileAccess.Write); + } - _temp.Dispose(); - _temp = null; + /// + /// Releases the unmanaged resources used by the and optionally releases the managed resources. + /// Copies the temporary file to the target file and then deletes the temporary file. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected override void Dispose(bool disposing) + { + if (_temp is null) + return; - base.Dispose(disposing); + _temp.Dispose(); + _temp = null; - File.Copy(_nameTemp, _name, true); + base.Dispose(disposing); - // SSD can copy file async - try - { - File.Delete(_nameTemp); - } - catch (Exception ex) - { - Trace.WriteLine(ex); - } - } + File.Copy(_nameTemp, _name, true); - /// - /// Clears all buffers for the current stream and causes any buffered data to be written to the underlying file. - /// - public override void Flush() + // SSD can copy file async + try { - _temp.Flush(); + File.Delete(_nameTemp); } - - /// - /// Sets the position within the temporary file stream. - /// - /// A byte offset relative to the origin parameter. - /// A value of type indicating the reference point used to obtain the new position. - /// The new position within the temporary file stream. - public override long Seek(long offset, SeekOrigin origin) + catch (Exception ex) { - return _temp.Seek(offset, origin); + Trace.WriteLine(ex); } + } - /// - /// Sets the length of the underlying temporary file stream. - /// - /// The desired length of the current stream in bytes. - public override void SetLength(long value) - { - _temp.SetLength(value); - } + /// + /// Clears all buffers for the current stream and causes any buffered data to be written to the underlying file. + /// + public override void Flush() + { + _temp.Flush(); + } - /// - /// Reading is not supported by the . - /// - /// The buffer to read data into. - /// The byte offset in buffer at which to begin storing the data read from the underlying stream. - /// The maximum number of bytes to be read from the underlying stream. - /// This method always throws a . - /// Always thrown. - public override int Read(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(); - } + /// + /// Sets the position within the temporary file stream. + /// + /// A byte offset relative to the origin parameter. + /// A value of type indicating the reference point used to obtain the new position. + /// The new position within the temporary file stream. + public override long Seek(long offset, SeekOrigin origin) + { + return _temp.Seek(offset, origin); + } - /// - /// Writes a sequence of bytes to the temporary file stream. - /// - /// An array of bytes. This is the buffer that contains the data to write to the temporary file. - /// The zero-based byte offset in buffer at which to begin copying bytes to the temporary file. - /// The number of bytes to be written to the temporary file. - public override void Write(byte[] buffer, int offset, int count) - { - _temp.Write(buffer, offset, count); - } + /// + /// Sets the length of the underlying temporary file stream. + /// + /// The desired length of the current stream in bytes. + public override void SetLength(long value) + { + _temp.SetLength(value); + } - /// - /// Gets a value indicating whether the temporary file stream supports reading. - /// Always returns false. - /// - public override bool CanRead => false; - - /// - /// Gets a value indicating whether the underlying temporary file stream supports seeking. - /// - public override bool CanSeek => _temp.CanSeek; - - /// - /// Gets a value indicating whether the underlying temporary file stream supports writing. - /// - public override bool CanWrite => _temp.CanWrite; - - /// - /// Gets the length in bytes of the underlying temporary file stream. - /// - public override long Length => _temp.Length; - - /// - /// Gets or sets the current position within the underlying temporary file stream. - /// - public override long Position - { - get => _temp.Position; - set => _temp.Position = value; - } + /// + /// Reading is not supported by the . + /// + /// The buffer to read data into. + /// The byte offset in buffer at which to begin storing the data read from the underlying stream. + /// The maximum number of bytes to be read from the underlying stream. + /// This method always throws a . + /// Always thrown. + public override int Read(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(); + } + + /// + /// Writes a sequence of bytes to the temporary file stream. + /// + /// An array of bytes. This is the buffer that contains the data to write to the temporary file. + /// The zero-based byte offset in buffer at which to begin copying bytes to the temporary file. + /// The number of bytes to be written to the temporary file. + public override void Write(byte[] buffer, int offset, int count) + { + _temp.Write(buffer, offset, count); + } + + /// + /// Gets a value indicating whether the temporary file stream supports reading. + /// Always returns false. + /// + public override bool CanRead => false; + + /// + /// Gets a value indicating whether the underlying temporary file stream supports seeking. + /// + public override bool CanSeek => _temp.CanSeek; + + /// + /// Gets a value indicating whether the underlying temporary file stream supports writing. + /// + public override bool CanWrite => _temp.CanWrite; + + /// + /// Gets the length in bytes of the underlying temporary file stream. + /// + public override long Length => _temp.Length; + + /// + /// Gets or sets the current position within the underlying temporary file stream. + /// + public override long Position + { + get => _temp.Position; + set => _temp.Position = value; } } \ No newline at end of file diff --git a/Server.Utils/ServiceLogListener.cs b/Server.Utils/ServiceLogListener.cs index 527e59a1..7f5bf3c4 100644 --- a/Server.Utils/ServiceLogListener.cs +++ b/Server.Utils/ServiceLogListener.cs @@ -1,71 +1,70 @@ -namespace Ecng.Server.Utils -{ - using System; - using System.Collections.Generic; +namespace Ecng.Server.Utils; + +using System; +using System.Collections.Generic; + +using Microsoft.Extensions.Logging; - using Microsoft.Extensions.Logging; +using Ecng.Serialization; +using Ecng.Logging; - using Ecng.Serialization; - using Ecng.Logging; +/// +/// The logger recording the data to a . +/// +public class ServiceLogListener : ILogListener +{ + private readonly ILogger _logger; /// - /// The logger recording the data to a . + /// Initializes a new instance of the . /// - public class ServiceLogListener : ILogListener + /// Logger. + public ServiceLogListener(ILogger logger) { - private readonly ILogger _logger; - - /// - /// Initializes a new instance of the . - /// - /// Logger. - public ServiceLogListener(ILogger logger) - { - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } - bool ILogListener.CanSave => false; + bool ILogListener.CanSave => false; - void IDisposable.Dispose() - { - GC.SuppressFinalize(this); - } + void IDisposable.Dispose() + { + GC.SuppressFinalize(this); + } - void IPersistable.Load(SettingsStorage storage) - { - } + void IPersistable.Load(SettingsStorage storage) + { + } - void IPersistable.Save(SettingsStorage storage) - { - } + void IPersistable.Save(SettingsStorage storage) + { + } - void ILogListener.WriteMessages(IEnumerable messages) + void ILogListener.WriteMessages(IEnumerable messages) + { + foreach (var message in messages) { - foreach (var message in messages) + switch (message.Level) { - switch (message.Level) - { - //case LogLevels.Inherit: - //case LogLevels.Off: - // break; - case LogLevels.Verbose: - _logger.Log(LogLevel.Trace, message.Message); - break; - case LogLevels.Debug: - _logger.Log(LogLevel.Debug, message.Message); - break; - case LogLevels.Info: - _logger.Log(LogLevel.Information, message.Message); - break; - case LogLevels.Warning: - _logger.Log(LogLevel.Warning, message.Message); - break; - case LogLevels.Error: - _logger.Log(LogLevel.Error, message.Message); - break; - //default: - // break; - } + //case LogLevels.Inherit: + //case LogLevels.Off: + // break; + case LogLevels.Verbose: + _logger.Log(LogLevel.Trace, message.Message); + break; + case LogLevels.Debug: + _logger.Log(LogLevel.Debug, message.Message); + break; + case LogLevels.Info: + _logger.Log(LogLevel.Information, message.Message); + break; + case LogLevels.Warning: + _logger.Log(LogLevel.Warning, message.Message); + break; + case LogLevels.Error: + _logger.Log(LogLevel.Error, message.Message); + break; + //default: + // break; } } } diff --git a/Server.Utils/ServicePath.cs b/Server.Utils/ServicePath.cs index de0666e1..5e457636 100644 --- a/Server.Utils/ServicePath.cs +++ b/Server.Utils/ServicePath.cs @@ -1,83 +1,82 @@ -namespace Ecng.Server.Utils -{ - using System; - using System.IO; - using System.Reflection; +namespace Ecng.Server.Utils; + +using System; +using System.IO; +using System.Reflection; - using Ecng.Serialization; - using Ecng.Logging; +using Ecng.Serialization; +using Ecng.Logging; - using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; +/// +/// Services IO utils. +/// +public static class ServicePath +{ /// - /// Services IO utils. + /// Current service directory. /// - public static class ServicePath - { - /// - /// Current service directory. - /// - public static string ServiceDir => Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)); + public static string ServiceDir => Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)); - /// - /// Get path to Data directory. - /// - public static string DataDir => Path.Combine(ServiceDir, "Data"); + /// + /// Get path to Data directory. + /// + public static string DataDir => Path.Combine(ServiceDir, "Data"); - /// - /// - /// - /// - /// - /// - /// - public static LogManager CreateLogManager(this ILogger logger, string dataDir, LogLevels defaultLevel) - { - if (logger is null) - throw new ArgumentNullException(nameof(logger)); + /// + /// + /// + /// + /// + /// + /// + public static LogManager CreateLogManager(this ILogger logger, string dataDir, LogLevels defaultLevel) + { + if (logger is null) + throw new ArgumentNullException(nameof(logger)); - Directory.CreateDirectory(dataDir); + Directory.CreateDirectory(dataDir); - var serializer = JsonSerializer.CreateDefault(); + var serializer = JsonSerializer.CreateDefault(); - var logSettingsFile = Path.Combine(dataDir, $"logManager.{serializer.FileExtension}"); + var logSettingsFile = Path.Combine(dataDir, $"logManager.{serializer.FileExtension}"); - var logManager = new LogManager - { - Application = { LogLevel = defaultLevel } - }; + var logManager = new LogManager + { + Application = { LogLevel = defaultLevel } + }; - if (File.Exists(logSettingsFile)) - { - logManager.Load(serializer.Deserialize(logSettingsFile)); - } - else + if (File.Exists(logSettingsFile)) + { + logManager.Load(serializer.Deserialize(logSettingsFile)); + } + else + { + logManager.Listeners.Add(new FileLogListener { - logManager.Listeners.Add(new FileLogListener - { - Append = true, - FileName = "logs", - LogDirectory = Path.Combine(dataDir, "Logs"), - SeparateByDates = SeparateByDateModes.SubDirectories, - HistoryPolicy = FileLogHistoryPolicies.Delete, - }); + Append = true, + FileName = "logs", + LogDirectory = Path.Combine(dataDir, "Logs"), + SeparateByDates = SeparateByDateModes.SubDirectories, + HistoryPolicy = FileLogHistoryPolicies.Delete, + }); - serializer.Serialize(logManager.Save(), logSettingsFile); - } + serializer.Serialize(logManager.Save(), logSettingsFile); + } - logManager.Listeners.Add(new ServiceLogListener(logger)); + logManager.Listeners.Add(new ServiceLogListener(logger)); - return logManager; - } + return logManager; + } - /// - /// Restart service. - /// - public static void Restart() - { - // https://stackoverflow.com/a/220451 - // solution required setup - Environment.Exit(1); - } + /// + /// Restart service. + /// + public static void Restart() + { + // https://stackoverflow.com/a/220451 + // solution required setup + Environment.Exit(1); } } \ No newline at end of file diff --git a/Server.Utils/ServiceSettingsBase.cs b/Server.Utils/ServiceSettingsBase.cs index afdabbef..f8ac25c5 100644 --- a/Server.Utils/ServiceSettingsBase.cs +++ b/Server.Utils/ServiceSettingsBase.cs @@ -1,27 +1,26 @@ -namespace Ecng.Server.Utils -{ - using Ecng.Logging; +namespace Ecng.Server.Utils; + +using Ecng.Logging; +/// +/// Base server settings. +/// +public abstract class ServiceSettingsBase +{ /// - /// Base server settings. + /// Initializes a new instance of the . /// - public abstract class ServiceSettingsBase + protected ServiceSettingsBase() { - /// - /// Initializes a new instance of the . - /// - protected ServiceSettingsBase() - { - } + } - /// - /// WebAPI address. - /// - public string WebApiAddress { get; set; } + /// + /// WebAPI address. + /// + public string WebApiAddress { get; set; } - /// - /// . - /// - public LogLevels LogLevel { get; set; } - } + /// + /// . + /// + public LogLevels LogLevel { get; set; } } \ No newline at end of file diff --git a/UnitTesting/AssertHelper.cs b/UnitTesting/AssertHelper.cs index b82d219c..0142dbe3 100644 --- a/UnitTesting/AssertHelper.cs +++ b/UnitTesting/AssertHelper.cs @@ -1,151 +1,150 @@ -namespace Ecng.UnitTesting +namespace Ecng.UnitTesting; + +using System; +using System.Security; + +using Ecng.Common; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +/// +/// Assert helper. +/// +public static class AssertHelper { - using System; - using System.Security; + /// + /// Asserts that the value is true. + /// + /// Value. + public static void AssertTrue(this bool value) + => Assert.IsTrue(value); + + /// + /// Asserts that the value is false. + /// + /// Value. + public static void AssertFalse(this bool value) + => Assert.IsFalse(value); + + /// + /// Asserts that the value is null. + /// + /// Value. + public static void AssertNull(this object value) + => Assert.IsNull(value); + + /// + /// Asserts that the value is not null. + /// + /// Value. + public static void AssertNull(this Exception value) + { + if (value != null) + throw value; + } + + /// + /// Asserts that the value is not null. + /// + /// Value. + public static void AssertNotNull(this object value) + => Assert.IsNotNull(value); + + /// + /// Asserts that the value is not null. + /// + /// Type of the value. + /// Value. + public static void AssertOfType(this object value) + => Assert.IsInstanceOfType(value, typeof(T)); + + /// + /// Asserts that the value is not null. + /// + /// Type of the value. + /// Value. + public static void AssertNotOfType(this object value) + => Assert.IsNotInstanceOfType(value, typeof(T)); + + /// + /// Asserts that the value is not null. + /// + /// Type of the value. + /// Value. + /// Expected value. + public static void AreEqual(this T value, T expected) + => value.AssertEqual(expected); + + /// + /// Asserts that the value is not null. + /// + /// Type of the value. + /// Value. + /// Expected value. + public static void AssertEqual(this T value, T expected) + { + if (value is SecureString str) + str.IsEqualTo(expected.To()).AssertTrue(); + else + Assert.AreEqual(expected, value); + } + + /// + /// Asserts that the value is not null. + /// + /// Value. + /// Expected value. + /// Delta for comparing floating point numbers. + public static void AssertEqual(this double value, double expected, double delta) + => Assert.AreEqual(expected, value, delta); + + /// + /// Asserts that the value is not null. + /// + /// Value. + /// Expected value. + /// Delta for comparing floating point numbers. + public static void AssertEqual(this float value, float expected, float delta) + => Assert.AreEqual(expected, value, delta); + + /// + /// Asserts that the value is not null. + /// + /// Type of the value. + /// Value. + /// Expected value. + public static void AssertNotEqual(this T value, T expected) + => Assert.AreNotEqual(expected, value); - using Ecng.Common; + /// + /// Asserts that the value is not null. + /// + /// Type of the value. + /// Value. + /// Expected value. + public static void AssertSame(this T value, T expected) + => Assert.AreSame(expected, value); - using Microsoft.VisualStudio.TestTools.UnitTesting; + /// + /// Asserts that the value is not null. + /// + /// Type of the value. + /// Value. + /// Expected value. + public static void AssertNotSame(this T value, T expected) + => Assert.AreNotSame(expected, value); /// - /// Assert helper. + /// Asserts that the value is not null. /// - public static class AssertHelper + /// Value. + /// Expected value. + /// If , then null and empty strings are considered equal. + public static void AssertEqual(this string value, string expected, bool nullAsEmpty = false) { - /// - /// Asserts that the value is true. - /// - /// Value. - public static void AssertTrue(this bool value) - => Assert.IsTrue(value); - - /// - /// Asserts that the value is false. - /// - /// Value. - public static void AssertFalse(this bool value) - => Assert.IsFalse(value); - - /// - /// Asserts that the value is null. - /// - /// Value. - public static void AssertNull(this object value) - => Assert.IsNull(value); - - /// - /// Asserts that the value is not null. - /// - /// Value. - public static void AssertNull(this Exception value) - { - if (value != null) - throw value; - } - - /// - /// Asserts that the value is not null. - /// - /// Value. - public static void AssertNotNull(this object value) - => Assert.IsNotNull(value); - - /// - /// Asserts that the value is not null. - /// - /// Type of the value. - /// Value. - public static void AssertOfType(this object value) - => Assert.IsInstanceOfType(value, typeof(T)); - - /// - /// Asserts that the value is not null. - /// - /// Type of the value. - /// Value. - public static void AssertNotOfType(this object value) - => Assert.IsNotInstanceOfType(value, typeof(T)); - - /// - /// Asserts that the value is not null. - /// - /// Type of the value. - /// Value. - /// Expected value. - public static void AreEqual(this T value, T expected) - => value.AssertEqual(expected); - - /// - /// Asserts that the value is not null. - /// - /// Type of the value. - /// Value. - /// Expected value. - public static void AssertEqual(this T value, T expected) - { - if (value is SecureString str) - str.IsEqualTo(expected.To()).AssertTrue(); - else - Assert.AreEqual(expected, value); - } - - /// - /// Asserts that the value is not null. - /// - /// Value. - /// Expected value. - /// Delta for comparing floating point numbers. - public static void AssertEqual(this double value, double expected, double delta) - => Assert.AreEqual(expected, value, delta); - - /// - /// Asserts that the value is not null. - /// - /// Value. - /// Expected value. - /// Delta for comparing floating point numbers. - public static void AssertEqual(this float value, float expected, float delta) - => Assert.AreEqual(expected, value, delta); - - /// - /// Asserts that the value is not null. - /// - /// Type of the value. - /// Value. - /// Expected value. - public static void AssertNotEqual(this T value, T expected) - => Assert.AreNotEqual(expected, value); - - /// - /// Asserts that the value is not null. - /// - /// Type of the value. - /// Value. - /// Expected value. - public static void AssertSame(this T value, T expected) - => Assert.AreSame(expected, value); - - /// - /// Asserts that the value is not null. - /// - /// Type of the value. - /// Value. - /// Expected value. - public static void AssertNotSame(this T value, T expected) - => Assert.AreNotSame(expected, value); - - /// - /// Asserts that the value is not null. - /// - /// Value. - /// Expected value. - /// If , then null and empty strings are considered equal. - public static void AssertEqual(this string value, string expected, bool nullAsEmpty = false) - { - if (nullAsEmpty && value.IsEmpty() && expected.IsEmpty()) - return; - - value.AssertEqual(expected); - } + if (nullAsEmpty && value.IsEmpty() && expected.IsEmpty()) + return; + + value.AssertEqual(expected); } } \ No newline at end of file