IX.StandardExtensions documentation
Abstractions
There are a lot of abstractions available in IX.StandarExtensions that seek to eliminate the hard dependencies to classes. The most widely-used are abstractions for the System.IO classes.
System.IO abstractions
Using these classes and interfaces, you can abstract away File
, Directory
and Path
. These
abstractions are located in the IX.System.IO
namespace.
You can inject an IFile, for example, which abstracts away all functionality in the File
class:
private readonly IFile _fileShim;
In your code, you can use it as you would normally use the File
class, but, instead of calling
static methods, you call instance methods on your _fileShim
instance:
using (var fileStream = _fileShim.OpenRead(fileToOpenPath))
{
// Do something with the fileStream variable
}
Then, when testing, all methods can be mocked, as in the example below (using Moq):
Mock<IFile> fileShimMock = new(MockBehavior.Strict);
fileShimMock.Setup(p => p.OpenRead("somePath.txt"))
.Returns(myControlledStream);
// Do the test here
fileShimMock.Verify(p => p.OpenRead("somePath.txt"), Times.Once());
All methods of the static classes are abstracted, and are also abstracted to the highest level of the framework API that is actually usable in that framework. Apart from allowing all methods to be mocked, this abstraction allows any code written in any supported framework version to be written into easily-upgradeable forms.
System.Threading abstractions
Using these classes and interfaces, you can abstract away the AutoResetEvent
, ManualResetEvent
,
ManualResetEventSlim
and ReaderWriterLockSlim
classes. These abstractions are located in the
IX.System.Threading
namespace.
Furthermore, an extra layer of abstraction is added with the ISetResetEvent
and IReaderWriterLock
interfaces. These give complete control and visibility over thread synchronization mechanisms,
allowing developers to do in-depth tests of their code and verify both positive and negative aspects
at runtime.
To abstract an AutoResetEvent
, simply use the AutoResetEvent
class from the IX.System.Threading
namespace:
private IX.System.Threading.AutoResetEvent _resetEvent;
protected ISetResetEvent SynchronizationEvent => _resetEvent;
In code that needs to wait, you can use:
await SynchronizationEvent;
Or, if you prefer the older style, need to synchronously wait, or cannot use the await
keyword due
to language constraints, or need to deal with timeouts, you can use:
if (!SynchronizationEvent.WaitOne(_timeout))
{
// Do something here for timeout exceeded
}
// Do something here for no timeout exceeded
Then, in any code that needs to unlock threads, you can use:
SynchronizationEvent.Set();
This can then be easily mocked in a manner similar to this:
Moq<ISetResetEvent> eventMock = new(MockBehavior.Strict);
eventMock.Setup(p => p.WaitOne(someTimeout)).Returns(true);
eventMock.Setup(p => p.Set());
// Do some tests
eventMock.Verify(p => p.WaitOne(someTimeout), Times.Once());
eventMock.Verify(p => p.Set(), Times.Once());
Similarly, to abstract away ReaderWriterLockSlim
, you need only use the ReaderWriterLockSlim
class
from the IX.System.Threading
namespace:
IX.System.Threading.ReaderWriterLockSlim rwl = new();
You can then use its locking abilities like:
if (rwl.TryEnterReadLock())
{
// Do something when the lock couldn't be acquired
}
// Do something when the lock could be acquired
But the true strength of this abstraction lies in the ReaderWriterSynchronizedBase
class described
below.
Base classes
There are a few base classes available that remove the boilerplate that is required for a lot of advanced work. There are classes like:
DisposableBase
- a base class that implements theIDisposable
pattern in a developer-friendly waySynchronizationContextInvokerBase
- a base class that offers a way to invoke on synchronization contexts, depending on what was set at construction time or, maybe, a default - especially useful in UINotifyPropertyChangedBase
- a base class for objects that need to implement property change notificationsNotifyCollectionChangedInvokerBase
- a base class for collections that need to implement change notificationsReaderWriterSynchronizedBase
- a base class for objects that need thread synchronization and thread safety facilitiesViewModelBase
- a base class for the view-model pattern
DisposableBase
The DisposableBase
class implements IDisposable
and IAsyncDisposable
(where available), and
allows a class that inherits from it to select how to best implement that pattern to their own
needs.
For example, a chain for disposing within the managed context (IDisposable.Dispose
is called,
maybe as a result of a using
block) could be used like this:
protected override void DisposeManagedContext()
{
// Do something dispose-related here
base.DisposeManagedContext();
}
Since the DisposableBase always keeps a (thread-safe) marker of whether or not the Dispose
method
had been called or not, it is possible to automatically throw ObjectDisposedException
from your
methods:
public void DoSomething()
{
// Current object not disposed
this.ThrowIfCurrentObjectDisposed();
// ... do whatever you need to
}
Any class that inherits can also use invocations that run in case the object hasn't been already disposed:
public void DoSomething() =>
this.InvokeIfNotDisposed(() => /* ... do something ... */);
SynchronizationContextInvokerBase
The SynchronizationContextInvokerBase
class allows invocations on a set or on a default
synchronization context, like in events:
private void SomeButton_Clicked(
object? sender,
EventArgs e) =>
Invoke(() => { /* ... This runs on the synchronization context ... */ });
The Invoke
method can be configured to either invoke synchronously (default) or asynchyronously,
by setting the IX.StandardExtensions.ComponentModel.EnvironmentSettings.InvokeAsynchronously
static
property:
IX.StandardExtensions.ComponentModel.EnvironmentSettings.InvokeAsynchronously = true;
You can also explicitly request synchronous invocation:
private void SomeButton_Clicked(
object? sender,
EventArgs e) =>
InvokeSend(() => { /* ... This runs synchronously on the synchronization context ... */ });
...or asynchronous invocation:
private void SomeButton_Clicked(
object? sender,
EventArgs e) =>
InvokePost(() => { /* ... This runs asynchronously on the synchronization context ... */ });
You can either set a specific synchronization context through the protected constructor:
protected void MyClassConstructor()
: base(new DispatcherSynchronizationContext(Application.Dispatcher))
{
// ... do constructor stuff
}
...or you can set the default one in IX.StandardExtensions.ComponentModel.EnvironmentSettings
:
IX.StandardExtensions.ComponentModel.EnvironmentSettings.BackupSynchronizationContext = new DispatcherSynchronizationContext(Application.Dispatcher);
You can always suppress the current synchronization context (and use the default one) by setting:
IX.StandardExtensions.ComponentModel.EnvironmentSettings.AlwaysSuppressCurrentSynchronizationContext = true;
You can get the applicable synchronization context by calling the GetUsableSynchronizationContext
method, like such:
var currentSynchronizationContext = IX.StandardExtensions.ComponentModel.EnvironmentSettings.GetUsableSynchronizationContext();
ReaderWriterSynchronizedBase
The ReaderWriterSynchronizedBase
class uses a ReaderWriterLockSlim
behind the scenes (optionally
one that can be explicitly set through a constructor) to do safe and synchronized read or write
operations based on your requirements.
Any class that inherits from this class can try to acquire read locks:
using (this.AcquireReadLock())
{
// ... do stuff that's read-synchronized
}
...write locks:
using (this.AcquireWriteLock())
{
// ... do stuff that's write-synchronized
}
...or upgradeable locks (locks that may, in the future, transition from read to write locks, but are not guaranteed to do so):
using (var @lock = this.AcquireReadWriteLock())
{
// ... do stuff that's read-synchronized
if (shouldUpgrade)
{
@lock.Upgrade();
// .. do stuff that's write-synchronized
}
}
The locking mechanism can also be used in invocations (optionally coupled with dispose-check, as the
ReaderWriterSynchronizedBase
is also inheriting the DisposableBase
class):
// Example taken from the InMemoryLimitedPersistedQueue<T> class.
public override int Count =>
InvokeIfNotDisposed(
reference => reference.ReadLock(
referenceL2 => referenceL2.internalQueue.Count,
reference),
this);
The locks work like the reader/writer lock pattern:
- Any number of read locks can coexist at the same time
- Only one write lock can exist at the same time, and it cannot exist in parallel to any read locks
- Only one upgradeable read lock can coexist at the same time, and its read part behaves the same as any other read lock, while the upgraded write part behaves like a write lock
The class uses the ReaderWriterLockSlim
abstraction, and is fully mockable. However, implementers
and inheritors of this class need to provide some access to the constructor that accepts an instance
of an IReaderWriterLock?
.
Data generation
The IX.DataGeneration.DataGenerator
class handles most of the data generation, with
self-explanatory names like RandomIntegerArray
, RandomAlphaString, RandomAlphanumericStrnig
,
RandomLowercaseString
, RandomUppercaseString
RandomNumericString
, RandomSymbolString
,
RandomString
, RandomInteger
or RandomNegativeInteger
.
Optionally, there is a PredictableDataStore<T>
class, which acts as a read-only list of items
of type T
, and has the capacity to generate a series of items T
and optionally play them back
in the exact order in which they were generated. This class usually comes in handy when doing testing
with random data, in order to be able to validate that the code ran against a random but verifiable
sequence of items.
Undoable
Basic concepts
The IX.Undoable namespace contains classes that are used for implementing undo and redo capabilities for inheriting objects.
The pattern used is in these classes is based on the idea of state changes. When implementing an
undoable object (derived from the EditableItemBase
class), two methods need to be implemented:
RevertChanges
- this method is called to do revertions during undoDoChanges
- this method is called during redo
Each of these methods reveives a StateChangeBase
instance, which details all the changes that need
to be implemented. The developer can choose to implement all of the state changes as free as they
wish, or they can use some of the already-existing classes: PropertyStateChange
and
PropertyStateChange<T>
for signifying changes in properties, CompositeStateChange
for signaling
multiple changes at the same time, or SubItemStateChange
, which we'll talk about later.
State changes
Whenever something happens that can be undoable, two things need to take place:
First, a call to either AdvertiseStateChange
or AdvertisePropertyChange
needs to happen. Within
that call, the developer signals exactly what changes happen. For instance, a property that
advertises its state changes would look like:
public string TestProperty
{
get => testProperty;
set
{
if (testProperty == value)
{
return;
}
AdvertisePropertyChange(
nameof(TestProperty),
testProperty,
value);
testProperty = value;
RaisePropertyChanged(nameof(TestProperty));
}
}
Then, you can implement both DoChanges
and RevertChanges
:
protected override void DoChanges(StateChangeBase stateChange)
{
if (stateChange is PropertyStateChange<string> psts)
{
if (psts.PropertyName != nameof(TestProperty))
{
throw new InvalidOperationException(
"Undo/Redo advertised a state change that is not for the only property, some state is leaking.");
}
testProperty = psts.NewValue;
RaisePropertyChanged(nameof(TestProperty));
}
else
{
throw new InvalidOperationException(
"Undo/Redo advertised a state change that is of a different type than property, some state is leaking.");
}
}
protected override void RevertChanges(StateChangeBase stateChange)
{
if (stateChange is PropertyStateChange<string> psts)
{
if (psts.PropertyName != nameof(TestProperty))
{
throw new InvalidOperationException(
"Undo/Redo advertised a state change that is not for the only property, some state is leaking.");
}
testProperty = psts.OldValue;
RaisePropertyChanged(nameof(TestProperty));
}
else
{
throw new InvalidOperationException(
"Undo/Redo advertised a state change that is of a different type than property, some state is leaking.");
}
}
You can implement your own state change classes, or can use the existing ones.
Undo transactions
In case there need to be multiple changes at the same time, certain classes that implement undoable
patterns will use operation transactions, such as Observable classes with their
StartExplicitUndoBlockTransaction
method.
The easiest example for transactions comes from the unit tests for the undo capabilities of the
ObservableList<T>
class:
[Fact(DisplayName = "ObservableList complete explicit undo transaction block")]
public void UnitTest1()
{
// ARRANGE
using (var list = new ObservableList<int>(new[] { 1, 2, 3, 4, 5 }))
{
// ACT & ASSERT
list.RemoveAt(0);
Assert.Equal(
4,
list.Count);
using (OperationTransaction tc = list.StartExplicitUndoBlockTransaction())
{
list.RemoveAt(0);
list.RemoveAt(0);
list.RemoveAt(0);
tc.Success();
}
_ = Assert.Single(list);
list.Undo();
Assert.Equal(
4,
list.Count);
list.Redo();
_ = Assert.Single(list);
}
}
Undo contexts and capturing
Undoable patterns can have multiple levels of action. The most common undoable situation is when you
want to undo changes at a collection level, but, at the same time, also factor in individual items.
In such a case, collection types, such as those in Observable, may use CaptureIntoUndoContext
to
signal to an individual item that its undo/redo capabilities will now be integrated into a parent,
rather than having to handle them itself. As such, while the object itself still has to implement
its own DoChanges
and RevertChanges
methods, its undo/redo will be managed by its patent object
otherwise entirely.
CaptureIntoUndoContext
is not restricted to collections, any object can capture any logical child
within its undo context.
The opposite operation is ReleaseFromUndoContext
, which sets the undo/redo context on the object
and, from that point onwards, any undo/redo operations must be handled by the object itself.
A good example for capturing contexts and how they relate to one another is found in the unit tests
for ObservableCollectionBase<T>
:
public void UnitTest17()
{
// ARRANGE
using (var capturingList = new ObservableList<CapturedItem>(new[] { new CapturedItem() }))
{
// Capture into a parent context
using (var upperCapturingList = new ObservableList<ObservableList<CapturedItem>>
{
AutomaticallyCaptureSubItems = true,
})
{
Assert.Null(capturingList.ParentUndoContext);
upperCapturingList.Add(capturingList);
Assert.Equal(
upperCapturingList,
capturingList.ParentUndoContext);
Assert.Null(capturingList[0].ParentUndoContext);
// ACT
capturingList.AutomaticallyCaptureSubItems = true;
// ASSERT
Assert.Equal(
capturingList,
capturingList[0].ParentUndoContext);
}
}
}
You will notice, apart from how the contexts get translated in captured and logical parent objects,
that some implementations might even do automatic capturing, such as the ObservableList<T>
that
is pictured in the unit test, with its AutomaticallyCaptureSubItems
property.
The same unit tests class located here offer further examples on how to properly use undo contexts and transactions.
Observable
( 🚧 under construction 🚧 )
Contracts
( 🚧 under construction 🚧 )
Extensions
( 🚧 under construction 🚧 )
ForEach on an IEnumerable.
Given we have:
IEnumerable<someClass> someCollection;
We would call a method for each item of the collection like this:
foreach (var item in someCollection)
{
someMethod(item);
}
With the extension method, we could call it like this:
someCollection.ForEach(someMethod);
The same would hold true for an array.
Although, to be fair, if you're going to have a benchmark of:
i++;
...then the foreach cycle will be faster, since you will not have an extra method invocation.
As an extra bonus, you can run them using task parallel library (.NET Standard 1.1 and above only).
someCollection.ParallelForEach(someMethod);
Sequence Equals
The next example comes from the need to compare data. Comparison on arrays or enumerables (or between an array or an IEnumerable) has always been slightly burdensome. We have a helper for that:
if (someCollection.SequenceEquals(someOtherCollection))
{
// Do something
}
Component model extensions
( 🚧 under construction 🚧 )
Efficiency extensions
( 🚧 under construction 🚧 )
Event model extensions
( 🚧 under construction 🚧 )
Globalization extensions
( 🚧 under construction 🚧 )
Threading extensions
( 🚧 under construction 🚧 )
Special collections
( 🚧 under construction 🚧 )
Threading and interlocking extensions
( 🚧 under construction 🚧 )
Document metadata
Last update: 11th October, 2022