Added some helper classes for INotifyPropertyChanged; added unit tests for the ObservableSet class; documented the second Count property exposed by the multi dictionary

git-svn-id: file:///srv/devel/repo-conversion/nusu@262 d2e56fa2-650e-0410-a79f-9358c0239efd
This commit is contained in:
Markus Ewald 2012-03-03 10:57:08 +00:00
parent df169e376a
commit 1a05bf9d63
13 changed files with 1175 additions and 139 deletions

View File

@ -57,9 +57,6 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Source\AffineThreadPool.Test.cs">
<DependentUpon>AffineThreadPool.cs</DependentUpon>
</Compile>
<Compile Include="Source\Cloning\CloneFactoryTest.cs" /> <Compile Include="Source\Cloning\CloneFactoryTest.cs" />
<Compile Include="Source\Cloning\ClonerHelpers.cs" /> <Compile Include="Source\Cloning\ClonerHelpers.cs" />
<Compile Include="Source\Cloning\ClonerHelpers.Test.cs"> <Compile Include="Source\Cloning\ClonerHelpers.Test.cs">
@ -186,27 +183,6 @@
<Compile Include="Source\Collections\ReverseComparer.Test.cs"> <Compile Include="Source\Collections\ReverseComparer.Test.cs">
<DependentUpon>ReverseComparer.cs</DependentUpon> <DependentUpon>ReverseComparer.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Source\AffineThreadPool.cs" />
<Compile Include="Source\EnumHelper.cs" />
<Compile Include="Source\EnumHelper.Test.cs">
<DependentUpon>EnumHelper.cs</DependentUpon>
</Compile>
<Compile Include="Source\Plugins\PrototypeFactory.cs" />
<Compile Include="Source\Plugins\PrototypeFactory.Test.cs">
<DependentUpon>PrototypeFactory.cs</DependentUpon>
</Compile>
<Compile Include="Source\Semaphore.cs" />
<Compile Include="Source\Semaphore.Test.cs">
<DependentUpon>Semaphore.cs</DependentUpon>
</Compile>
<Compile Include="Source\IO\PartialStream.cs" />
<Compile Include="Source\IO\PartialStream.Test.cs">
<DependentUpon>PartialStream.cs</DependentUpon>
</Compile>
<Compile Include="Source\IO\RingMemoryStream.cs" />
<Compile Include="Source\IO\RingMemoryStream.Test.cs">
<DependentUpon>RingMemoryStream.cs</DependentUpon>
</Compile>
<Compile Include="Source\Collections\TransformingReadOnlyCollection.cs" /> <Compile Include="Source\Collections\TransformingReadOnlyCollection.cs" />
<Compile Include="Source\Collections\TransformingReadOnlyCollection.Interfaces.cs"> <Compile Include="Source\Collections\TransformingReadOnlyCollection.Interfaces.cs">
<DependentUpon>TransformingReadOnlyCollection.cs</DependentUpon> <DependentUpon>TransformingReadOnlyCollection.cs</DependentUpon>
@ -221,13 +197,17 @@
<Compile Include="Source\Collections\WeakCollection.Test.cs"> <Compile Include="Source\Collections\WeakCollection.Test.cs">
<DependentUpon>WeakCollection.cs</DependentUpon> <DependentUpon>WeakCollection.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Source\FloatHelper.cs" /> <Compile Include="Source\IO\PartialStream.cs" />
<Compile Include="Source\FloatHelper.Test.cs"> <Compile Include="Source\IO\PartialStream.Test.cs">
<DependentUpon>FloatHelper.cs</DependentUpon> <DependentUpon>PartialStream.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Source\IntegerHelper.cs" /> <Compile Include="Source\IO\RingMemoryStream.cs" />
<Compile Include="Source\IntegerHelper.Test.cs"> <Compile Include="Source\IO\RingMemoryStream.Test.cs">
<DependentUpon>IntegerHelper.cs</DependentUpon> <DependentUpon>RingMemoryStream.cs</DependentUpon>
</Compile>
<Compile Include="Source\IO\ChainStream.cs" />
<Compile Include="Source\IO\ChainStream.Test.cs">
<DependentUpon>ChainStream.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Source\Licensing\LicenseKey.cs" /> <Compile Include="Source\Licensing\LicenseKey.cs" />
<Compile Include="Source\Licensing\LicenseKey.Test.cs"> <Compile Include="Source\Licensing\LicenseKey.Test.cs">
@ -246,10 +226,6 @@
<Compile Include="Source\Parsing\CommandLine.Parser.cs"> <Compile Include="Source\Parsing\CommandLine.Parser.cs">
<DependentUpon>CommandLine.cs</DependentUpon> <DependentUpon>CommandLine.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Source\PathHelper.cs" />
<Compile Include="Source\PathHelper.Test.cs">
<DependentUpon>PathHelper.cs</DependentUpon>
</Compile>
<Compile Include="Source\Plugins\AssemblyLoadEventArgs.cs" /> <Compile Include="Source\Plugins\AssemblyLoadEventArgs.cs" />
<Compile Include="Source\Plugins\AssemblyLoadEventArgs.Test.cs"> <Compile Include="Source\Plugins\AssemblyLoadEventArgs.Test.cs">
<DependentUpon>AssemblyLoadEventArgs.cs</DependentUpon> <DependentUpon>AssemblyLoadEventArgs.cs</DependentUpon>
@ -261,6 +237,10 @@
<Compile Include="Source\Plugins\FactoryEmployer.Test.cs"> <Compile Include="Source\Plugins\FactoryEmployer.Test.cs">
<DependentUpon>FactoryEmployer.cs</DependentUpon> <DependentUpon>FactoryEmployer.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Source\Plugins\PrototypeFactory.cs" />
<Compile Include="Source\Plugins\PrototypeFactory.Test.cs">
<DependentUpon>PrototypeFactory.cs</DependentUpon>
</Compile>
<Compile Include="Source\Plugins\IAssemblyLoader.cs" /> <Compile Include="Source\Plugins\IAssemblyLoader.cs" />
<Compile Include="Source\Plugins\InstanceEmployer.Test.cs"> <Compile Include="Source\Plugins\InstanceEmployer.Test.cs">
<DependentUpon>InstanceEmployer.cs</DependentUpon> <DependentUpon>InstanceEmployer.cs</DependentUpon>
@ -284,14 +264,46 @@
<Compile Include="Source\Plugins\PluginRepository.Test.cs"> <Compile Include="Source\Plugins\PluginRepository.Test.cs">
<DependentUpon>PluginRepository.cs</DependentUpon> <DependentUpon>PluginRepository.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Source\PropertyChangedEventArgsHelper.cs" />
<Compile Include="Source\PropertyChangedEventArgsHelper.Test.cs">
<DependentUpon>PropertyChangedEventArgsHelper.cs</DependentUpon>
</Compile>
<Compile Include="Source\AffineThreadPool.cs" />
<Compile Include="Source\AffineThreadPool.Test.cs">
<DependentUpon>AffineThreadPool.cs</DependentUpon>
</Compile>
<Compile Include="Source\EnumHelper.cs" />
<Compile Include="Source\EnumHelper.Test.cs">
<DependentUpon>EnumHelper.cs</DependentUpon>
</Compile>
<Compile Include="Source\Observable.cs" />
<Compile Include="Source\Observable.Test.cs">
<DependentUpon>Observable.cs</DependentUpon>
</Compile>
<Compile Include="Source\ObservableHelper.cs" />
<Compile Include="Source\ObservableHelper.Test.cs">
<DependentUpon>ObservableHelper.cs</DependentUpon>
</Compile>
<Compile Include="Source\Semaphore.cs" />
<Compile Include="Source\Semaphore.Test.cs">
<DependentUpon>Semaphore.cs</DependentUpon>
</Compile>
<Compile Include="Source\FloatHelper.cs" />
<Compile Include="Source\FloatHelper.Test.cs">
<DependentUpon>FloatHelper.cs</DependentUpon>
</Compile>
<Compile Include="Source\IntegerHelper.cs" />
<Compile Include="Source\IntegerHelper.Test.cs">
<DependentUpon>IntegerHelper.cs</DependentUpon>
</Compile>
<Compile Include="Source\PathHelper.cs" />
<Compile Include="Source\PathHelper.Test.cs">
<DependentUpon>PathHelper.cs</DependentUpon>
</Compile>
<Compile Include="Source\Shared.cs" /> <Compile Include="Source\Shared.cs" />
<Compile Include="Source\Shared.Test.cs"> <Compile Include="Source\Shared.Test.cs">
<DependentUpon>Shared.cs</DependentUpon> <DependentUpon>Shared.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Source\IO\ChainStream.cs" />
<Compile Include="Source\IO\ChainStream.Test.cs">
<DependentUpon>ChainStream.cs</DependentUpon>
</Compile>
<Compile Include="Source\StringBuilderHelper.cs" /> <Compile Include="Source\StringBuilderHelper.cs" />
<Compile Include="Source\StringBuilderHelper.Test.cs"> <Compile Include="Source\StringBuilderHelper.Test.cs">
<DependentUpon>StringBuilderHelper.cs</DependentUpon> <DependentUpon>StringBuilderHelper.cs</DependentUpon>

View File

@ -22,7 +22,7 @@
<DebugType>full</DebugType> <DebugType>full</DebugType>
<Optimize>false</Optimize> <Optimize>false</Optimize>
<OutputPath>bin\xna-4.0-phone7\Debug\</OutputPath> <OutputPath>bin\xna-4.0-phone7\Debug\</OutputPath>
<DefineConstants>TRACE;DEBUG;WINDOWS_PHONE;NO_CLONING;NO_SERIALIZATION;NO_XMLSCHEMA;NO_SYSTEMEVENTS;NO_EXITCONTEXT;NO_SPECIALIZED_COLLECTIONS</DefineConstants> <DefineConstants>TRACE;DEBUG;WINDOWS_PHONE;NO_CLONING;NO_SERIALIZATION;NO_XMLSCHEMA;NO_SYSTEMEVENTS;NO_EXITCONTEXT;NO_SPECIALIZED_COLLECTIONS;NO_CONCURRENT_COLLECTIONS</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<NoStdLib>true</NoStdLib> <NoStdLib>true</NoStdLib>
@ -34,7 +34,7 @@
<DebugType>pdbonly</DebugType> <DebugType>pdbonly</DebugType>
<Optimize>true</Optimize> <Optimize>true</Optimize>
<OutputPath>bin\xna-4.0-phone7\Release\</OutputPath> <OutputPath>bin\xna-4.0-phone7\Release\</OutputPath>
<DefineConstants>TRACE;WINDOWS_PHONE;NO_CLONING;NO_SERIALIZATION;NO_XMLSCHEMA;NO_SYSTEMEVENTS;NO_EXITCONTEXT;NO_SPECIALIZED_COLLECTIONS</DefineConstants> <DefineConstants>TRACE;WINDOWS_PHONE;NO_CLONING;NO_SERIALIZATION;NO_XMLSCHEMA;NO_SYSTEMEVENTS;NO_EXITCONTEXT;NO_SPECIALIZED_COLLECTIONS;NO_CONCURRENT_COLLECTIONS</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<NoStdLib>true</NoStdLib> <NoStdLib>true</NoStdLib>
@ -88,9 +88,6 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Source\AffineThreadPool.Test.cs">
<DependentUpon>AffineThreadPool.cs</DependentUpon>
</Compile>
<Compile Include="Source\Cloning\CloneFactoryTest.cs" /> <Compile Include="Source\Cloning\CloneFactoryTest.cs" />
<Compile Include="Source\Cloning\ClonerHelpers.cs" /> <Compile Include="Source\Cloning\ClonerHelpers.cs" />
<Compile Include="Source\Cloning\ClonerHelpers.Test.cs"> <Compile Include="Source\Cloning\ClonerHelpers.Test.cs">
@ -217,27 +214,6 @@
<Compile Include="Source\Collections\ReverseComparer.Test.cs"> <Compile Include="Source\Collections\ReverseComparer.Test.cs">
<DependentUpon>ReverseComparer.cs</DependentUpon> <DependentUpon>ReverseComparer.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Source\AffineThreadPool.cs" />
<Compile Include="Source\EnumHelper.cs" />
<Compile Include="Source\EnumHelper.Test.cs">
<DependentUpon>EnumHelper.cs</DependentUpon>
</Compile>
<Compile Include="Source\Plugins\PrototypeFactory.cs" />
<Compile Include="Source\Plugins\PrototypeFactory.Test.cs">
<DependentUpon>PrototypeFactory.cs</DependentUpon>
</Compile>
<Compile Include="Source\Semaphore.cs" />
<Compile Include="Source\Semaphore.Test.cs">
<DependentUpon>Semaphore.cs</DependentUpon>
</Compile>
<Compile Include="Source\IO\PartialStream.cs" />
<Compile Include="Source\IO\PartialStream.Test.cs">
<DependentUpon>PartialStream.cs</DependentUpon>
</Compile>
<Compile Include="Source\IO\RingMemoryStream.cs" />
<Compile Include="Source\IO\RingMemoryStream.Test.cs">
<DependentUpon>RingMemoryStream.cs</DependentUpon>
</Compile>
<Compile Include="Source\Collections\TransformingReadOnlyCollection.cs" /> <Compile Include="Source\Collections\TransformingReadOnlyCollection.cs" />
<Compile Include="Source\Collections\TransformingReadOnlyCollection.Interfaces.cs"> <Compile Include="Source\Collections\TransformingReadOnlyCollection.Interfaces.cs">
<DependentUpon>TransformingReadOnlyCollection.cs</DependentUpon> <DependentUpon>TransformingReadOnlyCollection.cs</DependentUpon>
@ -252,13 +228,17 @@
<Compile Include="Source\Collections\WeakCollection.Test.cs"> <Compile Include="Source\Collections\WeakCollection.Test.cs">
<DependentUpon>WeakCollection.cs</DependentUpon> <DependentUpon>WeakCollection.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Source\FloatHelper.cs" /> <Compile Include="Source\IO\PartialStream.cs" />
<Compile Include="Source\FloatHelper.Test.cs"> <Compile Include="Source\IO\PartialStream.Test.cs">
<DependentUpon>FloatHelper.cs</DependentUpon> <DependentUpon>PartialStream.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Source\IntegerHelper.cs" /> <Compile Include="Source\IO\RingMemoryStream.cs" />
<Compile Include="Source\IntegerHelper.Test.cs"> <Compile Include="Source\IO\RingMemoryStream.Test.cs">
<DependentUpon>IntegerHelper.cs</DependentUpon> <DependentUpon>RingMemoryStream.cs</DependentUpon>
</Compile>
<Compile Include="Source\IO\ChainStream.cs" />
<Compile Include="Source\IO\ChainStream.Test.cs">
<DependentUpon>ChainStream.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Source\Licensing\LicenseKey.cs" /> <Compile Include="Source\Licensing\LicenseKey.cs" />
<Compile Include="Source\Licensing\LicenseKey.Test.cs"> <Compile Include="Source\Licensing\LicenseKey.Test.cs">
@ -277,10 +257,6 @@
<Compile Include="Source\Parsing\CommandLine.Parser.cs"> <Compile Include="Source\Parsing\CommandLine.Parser.cs">
<DependentUpon>CommandLine.cs</DependentUpon> <DependentUpon>CommandLine.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Source\PathHelper.cs" />
<Compile Include="Source\PathHelper.Test.cs">
<DependentUpon>PathHelper.cs</DependentUpon>
</Compile>
<Compile Include="Source\Plugins\AssemblyLoadEventArgs.cs" /> <Compile Include="Source\Plugins\AssemblyLoadEventArgs.cs" />
<Compile Include="Source\Plugins\AssemblyLoadEventArgs.Test.cs"> <Compile Include="Source\Plugins\AssemblyLoadEventArgs.Test.cs">
<DependentUpon>AssemblyLoadEventArgs.cs</DependentUpon> <DependentUpon>AssemblyLoadEventArgs.cs</DependentUpon>
@ -292,6 +268,10 @@
<Compile Include="Source\Plugins\FactoryEmployer.Test.cs"> <Compile Include="Source\Plugins\FactoryEmployer.Test.cs">
<DependentUpon>FactoryEmployer.cs</DependentUpon> <DependentUpon>FactoryEmployer.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Source\Plugins\PrototypeFactory.cs" />
<Compile Include="Source\Plugins\PrototypeFactory.Test.cs">
<DependentUpon>PrototypeFactory.cs</DependentUpon>
</Compile>
<Compile Include="Source\Plugins\IAssemblyLoader.cs" /> <Compile Include="Source\Plugins\IAssemblyLoader.cs" />
<Compile Include="Source\Plugins\InstanceEmployer.Test.cs"> <Compile Include="Source\Plugins\InstanceEmployer.Test.cs">
<DependentUpon>InstanceEmployer.cs</DependentUpon> <DependentUpon>InstanceEmployer.cs</DependentUpon>
@ -315,14 +295,46 @@
<Compile Include="Source\Plugins\PluginRepository.Test.cs"> <Compile Include="Source\Plugins\PluginRepository.Test.cs">
<DependentUpon>PluginRepository.cs</DependentUpon> <DependentUpon>PluginRepository.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Source\PropertyChangedEventArgsHelper.cs" />
<Compile Include="Source\PropertyChangedEventArgsHelper.Test.cs">
<DependentUpon>PropertyChangedEventArgsHelper.cs</DependentUpon>
</Compile>
<Compile Include="Source\AffineThreadPool.cs" />
<Compile Include="Source\AffineThreadPool.Test.cs">
<DependentUpon>AffineThreadPool.cs</DependentUpon>
</Compile>
<Compile Include="Source\EnumHelper.cs" />
<Compile Include="Source\EnumHelper.Test.cs">
<DependentUpon>EnumHelper.cs</DependentUpon>
</Compile>
<Compile Include="Source\Observable.cs" />
<Compile Include="Source\Observable.Test.cs">
<DependentUpon>Observable.cs</DependentUpon>
</Compile>
<Compile Include="Source\ObservableHelper.cs" />
<Compile Include="Source\ObservableHelper.Test.cs">
<DependentUpon>ObservableHelper.cs</DependentUpon>
</Compile>
<Compile Include="Source\Semaphore.cs" />
<Compile Include="Source\Semaphore.Test.cs">
<DependentUpon>Semaphore.cs</DependentUpon>
</Compile>
<Compile Include="Source\FloatHelper.cs" />
<Compile Include="Source\FloatHelper.Test.cs">
<DependentUpon>FloatHelper.cs</DependentUpon>
</Compile>
<Compile Include="Source\IntegerHelper.cs" />
<Compile Include="Source\IntegerHelper.Test.cs">
<DependentUpon>IntegerHelper.cs</DependentUpon>
</Compile>
<Compile Include="Source\PathHelper.cs" />
<Compile Include="Source\PathHelper.Test.cs">
<DependentUpon>PathHelper.cs</DependentUpon>
</Compile>
<Compile Include="Source\Shared.cs" /> <Compile Include="Source\Shared.cs" />
<Compile Include="Source\Shared.Test.cs"> <Compile Include="Source\Shared.Test.cs">
<DependentUpon>Shared.cs</DependentUpon> <DependentUpon>Shared.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Source\IO\ChainStream.cs" />
<Compile Include="Source\IO\ChainStream.Test.cs">
<DependentUpon>ChainStream.cs</DependentUpon>
</Compile>
<Compile Include="Source\StringBuilderHelper.cs" /> <Compile Include="Source\StringBuilderHelper.cs" />
<Compile Include="Source\StringBuilderHelper.Test.cs"> <Compile Include="Source\StringBuilderHelper.Test.cs">
<DependentUpon>StringBuilderHelper.cs</DependentUpon> <DependentUpon>StringBuilderHelper.cs</DependentUpon>

View File

@ -22,7 +22,7 @@
<DebugType>full</DebugType> <DebugType>full</DebugType>
<Optimize>false</Optimize> <Optimize>false</Optimize>
<OutputPath>bin\xna-4.0-xbox360\Debug\</OutputPath> <OutputPath>bin\xna-4.0-xbox360\Debug\</OutputPath>
<DefineConstants>TRACE;DEBUG;XBOX;XBOX360;NO_CLONING;NO_SERIALIZATION;NO_XMLSCHEMA;NO_SYSTEMEVENTS;NO_EXITCONTEXT;NO_SPECIALIZED_COLLECTIONS</DefineConstants> <DefineConstants>TRACE;DEBUG;XBOX;XBOX360;NO_CLONING;NO_SERIALIZATION;NO_XMLSCHEMA;NO_SYSTEMEVENTS;NO_EXITCONTEXT;NO_SPECIALIZED_COLLECTIONS;NO_LINQ_EXPRESSIONS;NO_CONCURRENT_COLLECTIONS</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<NoStdLib>true</NoStdLib> <NoStdLib>true</NoStdLib>
@ -34,7 +34,7 @@
<DebugType>pdbonly</DebugType> <DebugType>pdbonly</DebugType>
<Optimize>true</Optimize> <Optimize>true</Optimize>
<OutputPath>bin\xna-4.0-xbox360\Release\</OutputPath> <OutputPath>bin\xna-4.0-xbox360\Release\</OutputPath>
<DefineConstants>TRACE;XBOX;XBOX360;NO_CLONING;NO_SERIALIZATION;NO_XMLSCHEMA;NO_SYSTEMEVENTS;NO_EXITCONTEXT;NO_SPECIALIZED_COLLECTIONS</DefineConstants> <DefineConstants>TRACE;XBOX;XBOX360;NO_CLONING;NO_SERIALIZATION;NO_XMLSCHEMA;NO_SYSTEMEVENTS;NO_EXITCONTEXT;NO_SPECIALIZED_COLLECTIONS;NO_LINQ_EXPRESSIONS;NO_CONCURRENT_COLLECTIONS</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<NoStdLib>true</NoStdLib> <NoStdLib>true</NoStdLib>
@ -99,9 +99,6 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Source\AffineThreadPool.Test.cs">
<DependentUpon>AffineThreadPool.cs</DependentUpon>
</Compile>
<Compile Include="Source\Cloning\CloneFactoryTest.cs" /> <Compile Include="Source\Cloning\CloneFactoryTest.cs" />
<Compile Include="Source\Cloning\ClonerHelpers.cs" /> <Compile Include="Source\Cloning\ClonerHelpers.cs" />
<Compile Include="Source\Cloning\ClonerHelpers.Test.cs"> <Compile Include="Source\Cloning\ClonerHelpers.Test.cs">
@ -228,27 +225,6 @@
<Compile Include="Source\Collections\ReverseComparer.Test.cs"> <Compile Include="Source\Collections\ReverseComparer.Test.cs">
<DependentUpon>ReverseComparer.cs</DependentUpon> <DependentUpon>ReverseComparer.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Source\AffineThreadPool.cs" />
<Compile Include="Source\EnumHelper.cs" />
<Compile Include="Source\EnumHelper.Test.cs">
<DependentUpon>EnumHelper.cs</DependentUpon>
</Compile>
<Compile Include="Source\Plugins\PrototypeFactory.cs" />
<Compile Include="Source\Plugins\PrototypeFactory.Test.cs">
<DependentUpon>PrototypeFactory.cs</DependentUpon>
</Compile>
<Compile Include="Source\Semaphore.cs" />
<Compile Include="Source\Semaphore.Test.cs">
<DependentUpon>Semaphore.cs</DependentUpon>
</Compile>
<Compile Include="Source\IO\PartialStream.cs" />
<Compile Include="Source\IO\PartialStream.Test.cs">
<DependentUpon>PartialStream.cs</DependentUpon>
</Compile>
<Compile Include="Source\IO\RingMemoryStream.cs" />
<Compile Include="Source\IO\RingMemoryStream.Test.cs">
<DependentUpon>RingMemoryStream.cs</DependentUpon>
</Compile>
<Compile Include="Source\Collections\TransformingReadOnlyCollection.cs" /> <Compile Include="Source\Collections\TransformingReadOnlyCollection.cs" />
<Compile Include="Source\Collections\TransformingReadOnlyCollection.Interfaces.cs"> <Compile Include="Source\Collections\TransformingReadOnlyCollection.Interfaces.cs">
<DependentUpon>TransformingReadOnlyCollection.cs</DependentUpon> <DependentUpon>TransformingReadOnlyCollection.cs</DependentUpon>
@ -263,13 +239,17 @@
<Compile Include="Source\Collections\WeakCollection.Test.cs"> <Compile Include="Source\Collections\WeakCollection.Test.cs">
<DependentUpon>WeakCollection.cs</DependentUpon> <DependentUpon>WeakCollection.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Source\FloatHelper.cs" /> <Compile Include="Source\IO\PartialStream.cs" />
<Compile Include="Source\FloatHelper.Test.cs"> <Compile Include="Source\IO\PartialStream.Test.cs">
<DependentUpon>FloatHelper.cs</DependentUpon> <DependentUpon>PartialStream.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Source\IntegerHelper.cs" /> <Compile Include="Source\IO\RingMemoryStream.cs" />
<Compile Include="Source\IntegerHelper.Test.cs"> <Compile Include="Source\IO\RingMemoryStream.Test.cs">
<DependentUpon>IntegerHelper.cs</DependentUpon> <DependentUpon>RingMemoryStream.cs</DependentUpon>
</Compile>
<Compile Include="Source\IO\ChainStream.cs" />
<Compile Include="Source\IO\ChainStream.Test.cs">
<DependentUpon>ChainStream.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Source\Licensing\LicenseKey.cs" /> <Compile Include="Source\Licensing\LicenseKey.cs" />
<Compile Include="Source\Licensing\LicenseKey.Test.cs"> <Compile Include="Source\Licensing\LicenseKey.Test.cs">
@ -288,10 +268,6 @@
<Compile Include="Source\Parsing\CommandLine.Parser.cs"> <Compile Include="Source\Parsing\CommandLine.Parser.cs">
<DependentUpon>CommandLine.cs</DependentUpon> <DependentUpon>CommandLine.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Source\PathHelper.cs" />
<Compile Include="Source\PathHelper.Test.cs">
<DependentUpon>PathHelper.cs</DependentUpon>
</Compile>
<Compile Include="Source\Plugins\AssemblyLoadEventArgs.cs" /> <Compile Include="Source\Plugins\AssemblyLoadEventArgs.cs" />
<Compile Include="Source\Plugins\AssemblyLoadEventArgs.Test.cs"> <Compile Include="Source\Plugins\AssemblyLoadEventArgs.Test.cs">
<DependentUpon>AssemblyLoadEventArgs.cs</DependentUpon> <DependentUpon>AssemblyLoadEventArgs.cs</DependentUpon>
@ -303,6 +279,10 @@
<Compile Include="Source\Plugins\FactoryEmployer.Test.cs"> <Compile Include="Source\Plugins\FactoryEmployer.Test.cs">
<DependentUpon>FactoryEmployer.cs</DependentUpon> <DependentUpon>FactoryEmployer.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Source\Plugins\PrototypeFactory.cs" />
<Compile Include="Source\Plugins\PrototypeFactory.Test.cs">
<DependentUpon>PrototypeFactory.cs</DependentUpon>
</Compile>
<Compile Include="Source\Plugins\IAssemblyLoader.cs" /> <Compile Include="Source\Plugins\IAssemblyLoader.cs" />
<Compile Include="Source\Plugins\InstanceEmployer.Test.cs"> <Compile Include="Source\Plugins\InstanceEmployer.Test.cs">
<DependentUpon>InstanceEmployer.cs</DependentUpon> <DependentUpon>InstanceEmployer.cs</DependentUpon>
@ -326,14 +306,46 @@
<Compile Include="Source\Plugins\PluginRepository.Test.cs"> <Compile Include="Source\Plugins\PluginRepository.Test.cs">
<DependentUpon>PluginRepository.cs</DependentUpon> <DependentUpon>PluginRepository.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Source\PropertyChangedEventArgsHelper.cs" />
<Compile Include="Source\PropertyChangedEventArgsHelper.Test.cs">
<DependentUpon>PropertyChangedEventArgsHelper.cs</DependentUpon>
</Compile>
<Compile Include="Source\AffineThreadPool.cs" />
<Compile Include="Source\AffineThreadPool.Test.cs">
<DependentUpon>AffineThreadPool.cs</DependentUpon>
</Compile>
<Compile Include="Source\EnumHelper.cs" />
<Compile Include="Source\EnumHelper.Test.cs">
<DependentUpon>EnumHelper.cs</DependentUpon>
</Compile>
<Compile Include="Source\Observable.cs" />
<Compile Include="Source\Observable.Test.cs">
<DependentUpon>Observable.cs</DependentUpon>
</Compile>
<Compile Include="Source\ObservableHelper.cs" />
<Compile Include="Source\ObservableHelper.Test.cs">
<DependentUpon>ObservableHelper.cs</DependentUpon>
</Compile>
<Compile Include="Source\Semaphore.cs" />
<Compile Include="Source\Semaphore.Test.cs">
<DependentUpon>Semaphore.cs</DependentUpon>
</Compile>
<Compile Include="Source\FloatHelper.cs" />
<Compile Include="Source\FloatHelper.Test.cs">
<DependentUpon>FloatHelper.cs</DependentUpon>
</Compile>
<Compile Include="Source\IntegerHelper.cs" />
<Compile Include="Source\IntegerHelper.Test.cs">
<DependentUpon>IntegerHelper.cs</DependentUpon>
</Compile>
<Compile Include="Source\PathHelper.cs" />
<Compile Include="Source\PathHelper.Test.cs">
<DependentUpon>PathHelper.cs</DependentUpon>
</Compile>
<Compile Include="Source\Shared.cs" /> <Compile Include="Source\Shared.cs" />
<Compile Include="Source\Shared.Test.cs"> <Compile Include="Source\Shared.Test.cs">
<DependentUpon>Shared.cs</DependentUpon> <DependentUpon>Shared.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Source\IO\ChainStream.cs" />
<Compile Include="Source\IO\ChainStream.Test.cs">
<DependentUpon>ChainStream.cs</DependentUpon>
</Compile>
<Compile Include="Source\StringBuilderHelper.cs" /> <Compile Include="Source\StringBuilderHelper.cs" />
<Compile Include="Source\StringBuilderHelper.Test.cs"> <Compile Include="Source\StringBuilderHelper.Test.cs">
<DependentUpon>StringBuilderHelper.cs</DependentUpon> <DependentUpon>StringBuilderHelper.cs</DependentUpon>

View File

@ -239,9 +239,25 @@ namespace Nuclex.Support.Collections {
return this.typedDictionary.GetEnumerator(); return this.typedDictionary.GetEnumerator();
} }
/// <summary>Removes the specified key/value pair from the dictionary</summary> /// <summary>Number of unique keys in the dictionary</summary>
/// <param name="item">Key/value pair that will be removed</param> /// <remarks>
/// <returns>True if the key/value pair was contained in the dictionary</returns> /// <para>
/// This Count property returns a different value from the main interface of
/// the multi dictionary to stay consistent with the implemented interfaces.
/// </para>
/// <para>
/// If you cast a multi dictionary to a collection of collections, the count
/// property of the outer collection should, of course, be the number of inner
/// collections it contains (and not the sum of the items contained in all of
/// the inner collections).
/// </para>
/// <para>
/// If you use the count property in the main interface of the multi dictionary,
/// the value collections are hidden (it behaves as if the key was in the
/// dictionary multiple times), so now the sum of all key-value pairs should
/// be returned.
/// </para>
/// </remarks>
int ICollection<KeyValuePair<TKey, ICollection<TValue>>>.Count { int ICollection<KeyValuePair<TKey, ICollection<TValue>>>.Count {
get { return this.typedDictionary.Count; } get { return this.typedDictionary.Count; }
} }

View File

@ -39,22 +39,46 @@ namespace Nuclex.Support.Collections {
public interface IObservableCollectionSubscriber<TItem> { public interface IObservableCollectionSubscriber<TItem> {
/// <summary>Raised when an item has been added to the collection</summary> /// <summary>Called when an item has been added to the collection</summary>
event EventHandler<ItemEventArgs<TItem>> ItemAdded; void ItemAdded(object sender, ItemEventArgs<TItem> arguments);
/// <summary>Raised when an item is removed from the collection</summary> /// <summary>Called when an item is removed from the collection</summary>
event EventHandler<ItemEventArgs<TItem>> ItemRemoved; void ItemRemoved(object sender, ItemEventArgs<TItem> arguments);
/// <summary>Raised when an item is replaced in the collection</summary> /// <summary>Called when an item is replaced in the collection</summary>
event EventHandler<ItemReplaceEventArgs<TItem>> ItemReplaced; void ItemReplaced(object sender, ItemReplaceEventArgs<TItem> arguments);
/// <summary>Raised when the collection is about to be cleared</summary> /// <summary>Called when the collection is about to be cleared</summary>
event EventHandler Clearing; void Clearing(object sender, EventArgs arguments);
/// <summary>Raised when the collection has been cleared</summary> /// <summary>Called when the collection has been cleared</summary>
event EventHandler Cleared; void Cleared(object sender, EventArgs arguments);
} }
#endregion // interface IObservableCollectionSubscriber<TItem> #endregion // interface IObservableCollectionSubscriber<TItem>
/// <summary>Called before each test is run</summary>
[SetUp]
public void Setup() {
this.mockFactory = new MockFactory();
this.observableSet = new ObservableSet<int>();
this.subscriber = this.mockFactory.CreateMock<IObservableCollectionSubscriber<int>>();
this.observableSet.ItemAdded += this.subscriber.MockObject.ItemAdded;
this.observableSet.ItemRemoved += this.subscriber.MockObject.ItemRemoved;
this.observableSet.ItemReplaced += this.subscriber.MockObject.ItemReplaced;
this.observableSet.Clearing += this.subscriber.MockObject.Clearing;
this.observableSet.Cleared += this.subscriber.MockObject.Cleared;
}
/// <summary>Called after each test has run</summary>
[TearDown]
public void Teardown() {
if(this.mockFactory != null) {
this.mockFactory.VerifyAllExpectationsHaveBeenMet();
this.subscriber = null;
this.mockFactory.Dispose();
this.mockFactory = null;
}
}
/// <summary> /// <summary>
/// Verifies that the observable set has a default constructor /// Verifies that the observable set has a default constructor
@ -64,6 +88,119 @@ namespace Nuclex.Support.Collections {
Assert.IsNotNull(new ObservableSet<int>()); Assert.IsNotNull(new ObservableSet<int>());
} }
/// <summary>
/// Verifies that adding items to the set triggers the 'ItemAdded' event
/// </summary>
[Test]
public void AddingItemsTriggersEvent() {
this.subscriber.Expects.One.Method((s) => s.ItemAdded(null, null)).WithAnyArguments();
this.observableSet.Add(123);
}
/// <summary>
/// Verifies that adding items to the set triggers the 'ItemAdded' event
/// </summary>
[Test]
public void AddingAlreadyContainedItemDoesNotTriggerEvent() {
this.subscriber.Expects.One.Method((s) => s.ItemAdded(null, null)).WithAnyArguments();
this.observableSet.Add(123);
this.subscriber.Expects.No.Method((s) => s.ItemAdded(null, null)).WithAnyArguments();
this.observableSet.Add(123);
}
/// <summary>
/// Verifies that excepting the set with itself empties the set
/// </summary>
[Test]
public void ExceptWithSelfEmptiesSet() {
var set = new ObservableSet<int>();
set.Add(1);
set.Add(2);
set.Add(3);
Assert.AreEqual(3, set.Count);
set.ExceptWith(set);
Assert.AreEqual(0, set.Count);
}
/// <summary>
/// Verifies that a set can be excepted with a collection
/// </summary>
[Test]
public void SetCanBeExceptedWithCollection() {
var set = new ObservableSet<int>();
set.Add(1);
set.Add(2);
var collection = new List<int>() { 1 };
Assert.AreEqual(2, set.Count);
set.ExceptWith(collection);
Assert.AreEqual(1, set.Count);
Assert.IsTrue(set.Contains(2));
}
/// <summary>
/// Verifies that a set can be intersected with a collection
/// </summary>
[Test]
public void SetCanBeIntersectedWithCollection() {
var set = new ObservableSet<int>();
set.Add(1);
set.Add(2);
var collection = new List<int>() { 1 };
Assert.AreEqual(2, set.Count);
set.IntersectWith(collection);
Assert.AreEqual(1, set.Count);
Assert.IsTrue(set.Contains(1));
}
/// <summary>
/// Verifies that it's possible to determine whether a set is a proper subset
/// or superset of another set
/// </summary>
[Test]
public void CanDetermineProperSubsetAndSuperset() {
var set1 = new ObservableSet<int>() { 1, 2, 3 };
var set2 = new ObservableSet<int>() { 1, 3 };
Assert.IsTrue(set1.IsProperSupersetOf(set2));
Assert.IsTrue(set2.IsProperSubsetOf(set1));
set2.Add(2);
Assert.IsFalse(set1.IsProperSupersetOf(set2));
Assert.IsFalse(set2.IsProperSubsetOf(set1));
}
/// <summary>
/// Verifies that it's possible to determine whether a set is a subset
/// or a superset of another set
/// </summary>
[Test]
public void CanDetermineSubsetAndSuperset() {
var set1 = new ObservableSet<int>() { 1, 2, 3 };
var set2 = new ObservableSet<int>() { 1, 2, 3 };
Assert.IsTrue(set1.IsSupersetOf(set2));
Assert.IsTrue(set2.IsSubsetOf(set1));
set2.Add(4);
Assert.IsFalse(set1.IsSupersetOf(set2));
Assert.IsFalse(set2.IsSubsetOf(set1));
}
/// <summary>Creates mock object for the test</summary>
private MockFactory mockFactory;
/// <summary>Observable set being tested</summary>
private ObservableSet<int> observableSet;
/// <summary>Subscriber for the observable set's events</summary>
private Mock<IObservableCollectionSubscriber<int>> subscriber;
} }
} // namespace Nuclex.Support.Collections } // namespace Nuclex.Support.Collections

View File

@ -109,11 +109,21 @@ namespace Nuclex.Support.Collections {
/// </summary> /// </summary>
/// <param name="other">Other set this set will be filtered by</param> /// <param name="other">Other set this set will be filtered by</param>
public void IntersectWith(IEnumerable<TItem> other) { public void IntersectWith(IEnumerable<TItem> other) {
foreach(TItem item in other) { var otherSet = other as ISet<TItem>;
if(!other.Contains(item)) { if(otherSet == null) {
this.set.Remove(item); otherSet = new HashSet<TItem>(other);
OnRemoved(item);
} }
var itemsToRemove = new List<TItem>();
foreach(TItem item in this.set) {
if(!otherSet.Contains(item)) {
itemsToRemove.Add(item);
}
}
for(int index = 0; index < itemsToRemove.Count; ++index) {
this.set.Remove(itemsToRemove[index]);
OnRemoved(itemsToRemove[index]);
} }
} }

170
Source/Observable.Test.cs Normal file
View File

@ -0,0 +1,170 @@
#region CPL License
/*
Nuclex Framework
Copyright (C) 2002-2012 Nuclex Development Labs
This library is free software; you can redistribute it and/or
modify it under the terms of the IBM Common Public License as
published by the IBM Corporation; either version 1.0 of the
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
IBM Common Public License for more details.
You should have received a copy of the IBM Common Public
License along with this library
*/
#endregion
#if UNITTEST
using System;
using System.ComponentModel;
using NUnit.Framework;
using NMock;
namespace Nuclex.Support {
/// <summary>Unit tests for observable class</summary>
[TestFixture]
internal class ObservableTest {
#region class TestObservable
/// <summary>Example class on which unit test generates change notifications</summary>
public class TestObservable : Observable {
/// <summary>Triggers the property changed event for the specified property</summary>
/// <param name="propertyName">
/// Name of the property that will be reported as changed
/// </param>
public void FirePropertyChanged(string propertyName) {
OnPropertyChanged(propertyName);
}
/// <summary>Fires the property changed event for the 'SomePropety' property</summary>
public void FireSomePropertyChanged() {
OnPropertyChanged(() => SomeProperty);
}
/// <summary>Example property that will be reported to have changed</summary>
public int SomeProperty { get; set; }
}
#endregion // class TestObservable
#region class MockedSubscriber
/// <summary>Mocked change notification subscriber</summary>
public class MockedSubscriber {
/// <summary>Called when the value of a property has changed</summary>
/// <param name="sender">Object of which a property has changed</param>
/// <param name="arguments">Contains the name of the changed property</param>
public void PropertyChanged(object sender, PropertyChangedEventArgs arguments) {
this.wasNotified = true;
this.changedPropertyName = arguments.PropertyName;
}
/// <summary>Whether the subscriber was notified of a property change</summary>
public bool WasNotified {
get { return this.wasNotified; }
}
/// <summary>
/// Checks whether a change notification for the specified property was received
/// </summary>
/// <param name="propertyName">Name of the property that will be checked for</param>
/// <returns>
/// True if a change notification for the specified property was received
/// </returns>
public bool WasNotifiedOfChangeTo(string propertyName) {
if(!this.wasNotified) {
return false;
}
if(string.IsNullOrEmpty(propertyName)) {
return string.IsNullOrEmpty(this.changedPropertyName);
}
return (propertyName == this.changedPropertyName);
}
/// <summary>Whether a change notification was received</summary>
private bool wasNotified;
/// <summary>Name of the property for which a change notification was received</summary>
private string changedPropertyName;
}
#endregion // class MockedSubscriber
/// <summary>Called before each unit test is run</summary>
[SetUp]
public void Setup() {
this.testObservable = new TestObservable();
this.subscriber = new MockedSubscriber();
this.testObservable.PropertyChanged += this.subscriber.PropertyChanged;
}
/// <summary>
/// Verifies that the name of the changed property can be specified manually
/// when triggering the PropertyChanged event
/// </summary>
[Test]
public void PropertyNameCanBeSpecifiedManually() {
this.testObservable.FirePropertyChanged("SomeProperty");
Assert.IsTrue(this.subscriber.WasNotifiedOfChangeTo("SomeProperty"));
}
#if DEBUG // The check is conditionally performed only in debug mode
/// <summary>
/// Verifies that specifying the name of a property that doesn't exist
/// causes an ArgumentException to be thrown
/// </summary>
[Test]
public void SpecifyingInvalidPropertyNameThrowsArgumentException() {
Assert.Throws<ArgumentException>(
delegate() { this.testObservable.FirePropertyChanged("DoesntExist"); }
);
}
#endif
/// <summary>
/// Verifies that the observable is capable of deducing the name of the property
/// from a lambda expression
/// </summary>
[Test]
public void PropertyNameCanBeDeducedFromLambdaExpression() {
this.testObservable.FireSomePropertyChanged();
Assert.IsTrue(this.subscriber.WasNotifiedOfChangeTo("SomeProperty"));
}
/// <summary>
/// Verifies that change notifications for all properties of a type can
/// be generated
/// </summary>
[Test]
public void WildcardChangeNotificationsCanBeSent() {
this.testObservable.FirePropertyChanged(string.Empty);
Assert.IsTrue(this.subscriber.WasNotifiedOfChangeTo(null));
this.testObservable.FirePropertyChanged(null);
Assert.IsTrue(this.subscriber.WasNotifiedOfChangeTo(string.Empty));
}
/// <summary>Observable object being tested</summary>
private TestObservable testObservable;
/// <summary>Subscriber to the observable object being tested</summary>
private MockedSubscriber subscriber;
}
} // namespace Nuclex.Support
#endif // UNITTEST

133
Source/Observable.cs Normal file
View File

@ -0,0 +1,133 @@
#region CPL License
/*
Nuclex Framework
Copyright (C) 2002-2012 Nuclex Development Labs
This library is free software; you can redistribute it and/or
modify it under the terms of the IBM Common Public License as
published by the IBM Corporation; either version 1.0 of the
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
IBM Common Public License for more details.
You should have received a copy of the IBM Common Public
License along with this library
*/
#endregion
using System;
using System.ComponentModel;
using System.Diagnostics;
#if !NO_LINQ_EXPRESSIONS
using System.Linq.Expressions;
#endif
using System.Reflection;
namespace Nuclex.Support {
/// <summary>Base class for objects that support property change notifications</summary>
#if !NO_SERIALIZATION
[Serializable]
#endif
public abstract class Observable : INotifyPropertyChanged {
/// <summary>Raised when a property of the instance has changed its value</summary>
#if !NO_SERIALIZATION
[field: NonSerialized]
#endif
public event PropertyChangedEventHandler PropertyChanged;
#if !NO_LINQ_EXPRESSIONS
/// <summary>Triggers the PropertyChanged event for the specified property</summary>
/// <param name="property">
/// Lambda expression for the property that will be reported to have changed
/// </param>
/// <remarks>
/// <para>
/// This notification should be fired post-change, i.e. when the property has
/// already changed its value.
/// </para>
/// <example>
/// <code>
/// public int Limit {
/// get { return this.limit; }
/// set {
/// if(value != this.limit) {
/// this.limit = value;
/// OnPropertyChanged(() => Limit);
/// }
/// }
/// }
/// </code>
/// </example>
/// </remarks>
protected void OnPropertyChanged<TProperty>(Expression<Func<TProperty>> property) {
PropertyChangedEventHandler copy = PropertyChanged;
if(copy != null) {
copy(this, PropertyChangedEventArgsHelper.GetArgumentsFor(property));
}
}
#endif // !NO_LINQ_EXPRESSIONS
/// <summary>Triggers the PropertyChanged event for the specified property</summary>
/// <param name="propertyName">Name of the property that has changed its value</param>
/// <remarks>
/// <para>
/// This notification should be fired post-change, i.e. when the property has
/// already changed its value. If possible, use the other overload of this
/// method to ensure the property name will be updated during F2 refactoring.
/// </para>
/// <example>
/// <code>
/// public int Limit {
/// get { return this.limit; }
/// set {
/// if(value != this.limit) {
/// this.limit = value;
/// OnPropertyChanged("Limit"); // Note: prefer lambda exp whenever possible
/// }
/// }
/// }
/// </code>
/// </example>
/// </remarks>
protected virtual void OnPropertyChanged(string propertyName) {
enforceChangedPropertyExists(propertyName);
PropertyChangedEventHandler copy = PropertyChanged;
if(copy != null) {
copy(this, PropertyChangedEventArgsHelper.GetArgumentsFor(propertyName));
}
}
/// <summary>Ensures that a property with the specified name exists in the type</summary>
/// <param name="propertyName">Property name that will be checked</param>
[Conditional("DEBUG")]
private void enforceChangedPropertyExists(string propertyName) {
// An empty string or null indicates that all properties have changed
if(string.IsNullOrEmpty(propertyName)) {
return;
}
// Any other string needs to match a property name
PropertyInfo property = GetType().GetProperty(propertyName);
if(property == null) {
throw new ArgumentException(
string.Format(
"Type '{0}' tried to raise a change notification for property '{1}', " +
"but no such property exists!",
GetType().Name, propertyName
),
"propertyName"
);
}
}
}
} // namespace Nuclex.Support

View File

@ -0,0 +1,91 @@
#region CPL License
/*
Nuclex Framework
Copyright (C) 2002-2012 Nuclex Development Labs
This library is free software; you can redistribute it and/or
modify it under the terms of the IBM Common Public License as
published by the IBM Corporation; either version 1.0 of the
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
IBM Common Public License for more details.
You should have received a copy of the IBM Common Public
License along with this library
*/
#endregion
#if UNITTEST
using System;
using NUnit.Framework;
namespace Nuclex.Support {
/// <summary>Unit tests for the observable helper</summary>
[TestFixture]
internal class ObservableHelperTest {
#region class TestReferenceType
/// <summary>Example class on which unit test generates change notifications</summary>
public class TestReferenceType {
/// <summary>Example property that will be reported to have changed</summary>
public int SomeProperty { get; set; }
}
#endregion // class TestReferenceType
#region struct TestValueType
/// <summary>Example class on which unit test generates change notifications</summary>
public struct TestValueType {
/// <summary>Example property that will be reported to have changed</summary>
public int SomeProperty { get; set; }
}
#endregion // struct TestValueType
/// <summary>
/// Verifies that the name of a property accessed in a lambda expression
/// can be obtained.
/// </summary>
[Test]
public void CanObtainPropertyNameFromLambdaExpression() {
string propertyName = ObservableHelper.GetPropertyName(
() => SomeReferenceType.SomeProperty
);
Assert.AreEqual("SomeProperty", propertyName);
}
/// <summary>
/// Verifies that the name of a property assigned in a lambda expression
/// can be obtained.
/// </summary>
[Test]
public void CanObtainPropertyNameFromBoxedLambdaExpression() {
string propertyName = ObservableHelper.GetPropertyName(
() => (object)(SomeValueType.SomeProperty)
);
Assert.AreEqual("SomeProperty", propertyName);
}
/// <summary>Helper used to construct lambda expressions</summary>
protected static TestReferenceType SomeReferenceType { get; set; }
/// <summary>Helper used to construct lambda expressions</summary>
protected static TestValueType SomeValueType { get; set; }
}
}
#endif // UNITTEST

View File

@ -0,0 +1,70 @@
#region CPL License
/*
Nuclex Framework
Copyright (C) 2002-2012 Nuclex Development Labs
This library is free software; you can redistribute it and/or
modify it under the terms of the IBM Common Public License as
published by the IBM Corporation; either version 1.0 of the
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
IBM Common Public License for more details.
You should have received a copy of the IBM Common Public
License along with this library
*/
#endregion
using System;
#if !NO_LINQ_EXPRESSIONS
using System.Linq.Expressions;
#endif
namespace Nuclex.Support {
/// <summary>Contains helper methods for observing property changed</summary>
public static class ObservableHelper {
#if !NO_LINQ_EXPRESSIONS
/// <summary>Obtains the name of a property from a lambda expression</summary>
/// <param name="property">
/// Lambda expression for the property whose name will be returned
/// </param>
/// <returns>The name of the property contained in the lamba expression</returns>
/// <remarks>
/// <para>
/// This method obtains the textual name of a property specified in a lambda
/// expression. By going through a lambda expression, the property will be
/// stated as actual code, allowing F2 refactoring to correctly update any
/// references to the property when it is renamed.
/// </para>
/// <example>
/// <code>
/// string propertyName = ObservableHelper.GetPropertyName(() => SomeValue);
/// Assert.AreEqual("SomeValue", propertyName);
/// </code>
/// </example>
/// </remarks>
public static string GetPropertyName<TValue>(Expression<Func<TValue>> property) {
var lambda = (LambdaExpression)property;
MemberExpression memberExpression;
{
var unaryExpression = lambda.Body as UnaryExpression;
if(unaryExpression != null) {
memberExpression = (MemberExpression)unaryExpression.Operand;
} else {
memberExpression = (MemberExpression)lambda.Body;
}
}
return memberExpression.Member.Name;
}
#endif // !NO_LINQ_EXPRESSIONS
}
} // namespace Nuclex.Support

View File

@ -0,0 +1,117 @@
#region CPL License
/*
Nuclex Framework
Copyright (C) 2002-2012 Nuclex Development Labs
This library is free software; you can redistribute it and/or
modify it under the terms of the IBM Common Public License as
published by the IBM Corporation; either version 1.0 of the
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
IBM Common Public License for more details.
You should have received a copy of the IBM Common Public
License along with this library
*/
#endregion
#if UNITTEST
using System;
using System.ComponentModel;
using NUnit.Framework;
namespace Nuclex.Support {
/// <summary>Unit tests for the property change event argument helper</summary>
[TestFixture]
internal class PropertyChangedEventArgsHelperTest {
#region class TestViewModel
/// <summary>Example class on which unit test generates change notifications</summary>
public class TestViewModel {
/// <summary>Example property that will be reported to have changed</summary>
public int SomeProperty { get; set; }
}
#endregion // class TestViewModel
/// <summary>
/// Verifies that a property change notification matching the property
/// passed to the AreAffecting() method is recognized
/// </summary>
[Test]
public void MatchingPropertyChangeNotificationIsRecognized() {
var arguments = new PropertyChangedEventArgs("SomeProperty");
Assert.IsTrue(arguments.AreAffecting(() => ViewModel.SomeProperty));
Assert.IsTrue(arguments.AreAffecting("SomeProperty"));
}
/// <summary>
/// Ensures that a mismatching property change notification will
/// not report the property as being affected.
/// </summary>
[Test]
public void MismatchingPropertyIsReportedAsUnaffected() {
var arguments = new PropertyChangedEventArgs("AnotherProperty");
Assert.IsFalse(arguments.AreAffecting(() => ViewModel.SomeProperty));
Assert.IsFalse(arguments.AreAffecting("SomeProperty"));
}
/// <summary>
/// Verifies that any specific property is reported as being affected
/// when the property change notification is a null wildcard
/// </summary>
[Test]
public void SpecificPropertyIsAffectedByNullWildcard() {
var nullArguments = new PropertyChangedEventArgs(null);
Assert.IsTrue(nullArguments.AreAffecting(() => ViewModel.SomeProperty));
Assert.IsTrue(nullArguments.AreAffecting("SomeProperty"));
}
/// <summary>
/// Verifies that any specific property is reported as being affected
/// when the property change notification is an empty wildcard
/// </summary>
[Test]
public void SpecificPropertyIsAffectedByEmptyWildcard() {
var emptyArguments = new PropertyChangedEventArgs(string.Empty);
Assert.IsTrue(emptyArguments.AreAffecting(() => ViewModel.SomeProperty));
Assert.IsTrue(emptyArguments.AreAffecting("SomeProperty"));
}
/// <summary>
/// Tests whether the helper can recognize a wildcard property change
/// notification using null as the wildcard.
/// </summary>
[Test]
public void NullWildcardIsRecognized() {
var nullArguments = new PropertyChangedEventArgs(null);
Assert.IsTrue(nullArguments.AffectAllProperties());
}
/// <summary>
/// Tests whether the helper can recognize a wildcard property change
/// notification using an empty string as the wildcard.
/// </summary>
[Test]
public void EmptyWildcardIsRecognized() {
var emptyArguments = new PropertyChangedEventArgs(string.Empty);
Assert.IsTrue(emptyArguments.AffectAllProperties());
}
/// <summary>Helper used to construct lambda expressions</summary>
protected static TestViewModel ViewModel { get; set; }
}
} // namespace Nuclex.Support
#endif // UNITTEST

View File

@ -0,0 +1,256 @@
#region CPL License
/*
Nuclex Framework
Copyright (C) 2002-2012 Nuclex Development Labs
This library is free software; you can redistribute it and/or
modify it under the terms of the IBM Common Public License as
published by the IBM Corporation; either version 1.0 of the
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
IBM Common Public License for more details.
You should have received a copy of the IBM Common Public
License along with this library
*/
#endregion
using System;
#if NO_CONCURRENT_COLLECTIONS
using System.Collections.Generic;
#else
using System.Collections.Concurrent;
#endif
using System.ComponentModel;
#if !NO_LINQ_EXPRESSIONS
using System.Linq.Expressions;
#endif
namespace Nuclex.Support {
/// <summary>Contains helper methods for property change notifications</summary>
public static class PropertyChangedEventArgsHelper {
/// <summary>
/// A property change event argument container that indicates that all
/// properties have changed their value.
/// </summary>
public static readonly PropertyChangedEventArgs Wildcard =
new PropertyChangedEventArgs(null);
/// <summary>Initializes a new property changed argument helper</summary>
static PropertyChangedEventArgsHelper() {
#if NO_CONCURRENT_COLLECTIONS
cache = new Dictionary<string, PropertyChangedEventArgs>();
#else
cache = new ConcurrentDictionary<string, PropertyChangedEventArgs>();
#endif
}
#if !NO_LINQ_EXPRESSIONS
/// <summary>
/// Provides a property change argument container for the specified property
/// </summary>
/// <param name="property">
/// Property for which an event argument container will be provided
/// </param>
/// <returns>The event argument container for a property of the specified name</returns>
/// <remarks>
/// <para>
/// This method transparently caches instances of the argument containers
/// to avoid feeding the garbage collector. A typical application only has
/// in the order of tens to hundreds of different properties for which changes
/// will be reported, making a cache to avoid garbage collections viable.
/// </para>
/// <example>
/// <code>
/// PropertyChangedEventArgs arguments =
/// PropertyChangedEventArgsHelper.GetArgumentsFor(() => SomeProperty);
/// </code>
/// </example>
/// </remarks>
public static PropertyChangedEventArgs GetArgumentsFor<TValue>(
Expression<Func<TValue>> property
) {
return GetArgumentsFor(ObservableHelper.GetPropertyName(property));
}
#endif
/// <summary>
/// Provides a property change argument container for the specified property
/// </summary>
/// <param name="propertyName">
/// Property for which an event argument container will be provided
/// </param>
/// <returns>The event argument container for a property of the specified name</returns>
/// <remarks>
/// <para>
/// This method transparently caches instances of the argument containers
/// to avoid feeding the garbage collector. A typical application only has
/// in the order of tens to hundreds of different properties for which changes
/// will be reported, making a cache to avoid garbage collections viable.
/// </para>
/// <example>
/// <code>
/// PropertyChangedEventArgs arguments =
/// PropertyChangedEventArgsHelper.GetArgumentsFor("SomeProperty");
/// </code>
/// </example>
/// </remarks>
public static PropertyChangedEventArgs GetArgumentsFor(string propertyName) {
if(string.IsNullOrEmpty(propertyName)) {
return Wildcard;
}
#if NO_CONCURRENT_COLLECTIONS
lock(cache) {
// Try to reuse the change notification if an instance already exists
PropertyChangedEventArgs arguments;
if(!cache.TryGetValue(propertyName, out arguments)) {
arguments = new PropertyChangedEventArgs(propertyName);
cache.Add(propertyName, arguments);
}
return arguments;
}
#else
// If an instance for this property already exists, just return it
PropertyChangedEventArgs arguments;
if(cache.TryGetValue(propertyName, out arguments)) {
return arguments;
}
// No instance existed (at least a short moment ago), so create a new one
return cache.GetOrAdd(propertyName, new PropertyChangedEventArgs(propertyName));
#endif
}
#if !NO_LINQ_EXPRESSIONS
/// <summary>
/// Determines whether the property change affects the specified property
/// </summary>
/// <typeparam name="TValue">
/// Type of the property that will be tested for being affected
/// </typeparam>
/// <param name="arguments">
/// Property change that has been reported by the observed object
/// </param>
/// <param name="property">Property that will be tested for being affected</param>
/// <returns>Whether the specified property is affected by the property change</returns>
/// <remarks>
/// <para>
/// By using this method, you can shorten the code needed to test whether
/// a property change notification affects a specific property. You also
/// avoid hardcoding the property name, which would have the adverse effect
/// of not updating the textual property names during F2 refactoring.
/// </para>
/// <example>
/// <code>
/// private void propertyChanged(object sender, PropertyChangedEventArgs arguments) {
/// if(arguments.AreAffecting(() => ViewModel.DisplayedValue)) {
/// updateDisplayedValueFromViewModel();
/// } // Do not use else if here or wildcards will not work
/// if(arguments.AreAffecting(() => ViewModel.OtherValue)) {
/// updateOtherValueFromViewModel();
/// }
/// }
/// </code>
/// </example>
/// </remarks>
public static bool AreAffecting<TValue>(
this PropertyChangedEventArgs arguments, Expression<Func<TValue>> property
) {
if(arguments.AffectAllProperties()) {
return true;
}
string propertyName = ObservableHelper.GetPropertyName(property);
return (arguments.PropertyName == propertyName);
}
#endif
/// <summary>
/// Determines whether the property change affects the specified property
/// </summary>
/// <typeparam name="TValue">
/// Type of the property that will be tested for being affected
/// </typeparam>
/// <param name="arguments">
/// Property change that has been reported by the observed object
/// </param>
/// <param name="propertyName">Property that will be tested for being affected</param>
/// <returns>Whether the specified property is affected by the property change</returns>
/// <remarks>
/// <para>
/// By using this method, you can shorten the code needed to test whether
/// a property change notification affects a specific property.
/// </para>
/// <example>
/// <code>
/// private void propertyChanged(object sender, PropertyChangedEventArgs arguments) {
/// if(arguments.AreAffecting("DisplayedValue")) {
/// updateDisplayedValueFromViewModel();
/// } // Do not use else if here or wildcards will not work
/// if(arguments.AreAffecting("OtherValue")) {
/// updateOtherValueFromViewModel();
/// }
/// }
/// </code>
/// </example>
/// </remarks>
public static bool AreAffecting(
this PropertyChangedEventArgs arguments, string propertyName
) {
if(arguments.AffectAllProperties()) {
return true;
}
return (arguments.PropertyName == propertyName);
}
/// <summary>Determines whether a property change notification is a wildcard</summary>
/// <param name="arguments">
/// Property change notification that will be checked on being a wildcard
/// </param>
/// <returns>
/// Whether the property change is a wildcard, indicating that all properties
/// have changed.
/// </returns>
/// <remarks>
/// <para>
/// As stated on MSDN: "The PropertyChanged event can indicate all properties
/// on the object have changed by using either Nothing or String.Empty as
/// the property name in the PropertyChangedEventArgs."
/// </para>
/// <para>
/// This method offers an expressive way of checking for that eventuality.
/// </para>
/// <example>
/// <code>
/// private void propertyChanged(object sender, PropertyChangedEventArgs arguments) {
/// if(arguments.AffectAllProperties()) {
/// // Do something
/// }
/// }
/// </code>
/// </example>
/// </remarks>
public static bool AffectAllProperties(this PropertyChangedEventArgs arguments) {
return string.IsNullOrEmpty(arguments.PropertyName);
}
/// <summary>
/// Caches PropertyChangedEventArgs instances to avoid feeding the garbage collector
/// </summary>
#if NO_CONCURRENT_COLLECTIONS
private static readonly Dictionary<string, PropertyChangedEventArgs> cache;
#else
private static readonly ConcurrentDictionary<string, PropertyChangedEventArgs> cache;
#endif
}
} // namespace Nuclex.Support