diff --git a/Livet.Code.sln b/Livet.Code.sln
index d0d71e6..c7fb21d 100644
--- a/Livet.Code.sln
+++ b/Livet.Code.sln
@@ -1,23 +1,27 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 15
-VisualStudioVersion = 15.0.26124.0
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29324.140
MinimumVisualStudioVersion = 15.0.26124.0
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LivetCask", "LivetCask\LivetCask.csproj", "{6A4E6038-17ED-4CBA-8261-359A4436F9E7}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LivetCask", "LivetCask\LivetCask.csproj", "{6A4E6038-17ED-4CBA-8261-359A4436F9E7}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LivetCask.Behaviors", "LivetCask.Behaviors\LivetCask.Behaviors.csproj", "{9CE78389-63DA-4681-98F1-DEDBE7982E58}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LivetCask.Behaviors", "LivetCask.Behaviors\LivetCask.Behaviors.csproj", "{9CE78389-63DA-4681-98F1-DEDBE7982E58}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LivetCask.Converters", "LivetCask.Converters\LivetCask.Converters.csproj", "{E216160C-7A4E-41D8-BE64-DFF869B2FB32}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LivetCask.Converters", "LivetCask.Converters\LivetCask.Converters.csproj", "{E216160C-7A4E-41D8-BE64-DFF869B2FB32}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LivetCask.Core", "LivetCask.Core\LivetCask.Core.csproj", "{74FFEBB4-5DE7-4F0B-BB8A-4915A1034383}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LivetCask.Core", "LivetCask.Core\LivetCask.Core.csproj", "{74FFEBB4-5DE7-4F0B-BB8A-4915A1034383}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LivetCask.EventListeners", "LivetCask.EventListeners\LivetCask.EventListeners.csproj", "{BD64E000-2725-4AD9-8505-C8E29C9A5D85}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LivetCask.EventListeners", "LivetCask.EventListeners\LivetCask.EventListeners.csproj", "{BD64E000-2725-4AD9-8505-C8E29C9A5D85}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LivetCask.Extensions", "LivetCask.Extensions\LivetCask.Extensions.csproj", "{2A113BF2-287B-4BF3-A4AC-2BF53153AE1D}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LivetCask.Extensions", "LivetCask.Extensions\LivetCask.Extensions.csproj", "{2A113BF2-287B-4BF3-A4AC-2BF53153AE1D}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LivetCask.Messaging", "LivetCask.Messaging\LivetCask.Messaging.csproj", "{94C5EDA8-AEAF-49C2-AA45-9F99A0A3907A}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LivetCask.Messaging", "LivetCask.Messaging\LivetCask.Messaging.csproj", "{94C5EDA8-AEAF-49C2-AA45-9F99A0A3907A}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LivetCask.Collections", "LivetCask.Collections\LivetCask.Collections.csproj", "{D9CCD1BF-29E4-4FA8-9F97-9A035FF14DAB}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LivetCask.Collections", "LivetCask.Collections\LivetCask.Collections.csproj", "{D9CCD1BF-29E4-4FA8-9F97-9A035FF14DAB}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LivetCask.StatefulModel", "LivetCask.StatefulModel\LivetCask.StatefulModel.csproj", "{710B9565-86D5-4473-8E64-70343D4F4021}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LivetCask.Mvvm", "LivetCask.Mvvm\LivetCask.Mvvm.csproj", "{C56DF371-8683-4023-8D86-C49252229838}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -28,9 +32,6 @@ Global
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{6A4E6038-17ED-4CBA-8261-359A4436F9E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6A4E6038-17ED-4CBA-8261-359A4436F9E7}.Debug|Any CPU.Build.0 = Debug|Any CPU
@@ -128,5 +129,35 @@ Global
{D9CCD1BF-29E4-4FA8-9F97-9A035FF14DAB}.Release|x64.Build.0 = Release|Any CPU
{D9CCD1BF-29E4-4FA8-9F97-9A035FF14DAB}.Release|x86.ActiveCfg = Release|Any CPU
{D9CCD1BF-29E4-4FA8-9F97-9A035FF14DAB}.Release|x86.Build.0 = Release|Any CPU
+ {710B9565-86D5-4473-8E64-70343D4F4021}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {710B9565-86D5-4473-8E64-70343D4F4021}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {710B9565-86D5-4473-8E64-70343D4F4021}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {710B9565-86D5-4473-8E64-70343D4F4021}.Debug|x64.Build.0 = Debug|Any CPU
+ {710B9565-86D5-4473-8E64-70343D4F4021}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {710B9565-86D5-4473-8E64-70343D4F4021}.Debug|x86.Build.0 = Debug|Any CPU
+ {710B9565-86D5-4473-8E64-70343D4F4021}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {710B9565-86D5-4473-8E64-70343D4F4021}.Release|Any CPU.Build.0 = Release|Any CPU
+ {710B9565-86D5-4473-8E64-70343D4F4021}.Release|x64.ActiveCfg = Release|Any CPU
+ {710B9565-86D5-4473-8E64-70343D4F4021}.Release|x64.Build.0 = Release|Any CPU
+ {710B9565-86D5-4473-8E64-70343D4F4021}.Release|x86.ActiveCfg = Release|Any CPU
+ {710B9565-86D5-4473-8E64-70343D4F4021}.Release|x86.Build.0 = Release|Any CPU
+ {C56DF371-8683-4023-8D86-C49252229838}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C56DF371-8683-4023-8D86-C49252229838}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C56DF371-8683-4023-8D86-C49252229838}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {C56DF371-8683-4023-8D86-C49252229838}.Debug|x64.Build.0 = Debug|Any CPU
+ {C56DF371-8683-4023-8D86-C49252229838}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C56DF371-8683-4023-8D86-C49252229838}.Debug|x86.Build.0 = Debug|Any CPU
+ {C56DF371-8683-4023-8D86-C49252229838}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C56DF371-8683-4023-8D86-C49252229838}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C56DF371-8683-4023-8D86-C49252229838}.Release|x64.ActiveCfg = Release|Any CPU
+ {C56DF371-8683-4023-8D86-C49252229838}.Release|x64.Build.0 = Release|Any CPU
+ {C56DF371-8683-4023-8D86-C49252229838}.Release|x86.ActiveCfg = Release|Any CPU
+ {C56DF371-8683-4023-8D86-C49252229838}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {0AE35214-2220-49A3-B3E2-0316B3C80891}
EndGlobalSection
EndGlobal
diff --git a/Livet.Test/Livet.Test.csproj b/Livet.Test/Livet.Test.csproj
index cec1dfc..da2b8fa 100644
--- a/Livet.Test/Livet.Test.csproj
+++ b/Livet.Test/Livet.Test.csproj
@@ -7,13 +7,15 @@
netcoreapp3.0;net452
true
false
+ Livet.NUnit
-
-
+
+
+
@@ -25,6 +27,7 @@
+
diff --git a/Livet.Test/StatefulModel/ConvertingTest.cs b/Livet.Test/StatefulModel/ConvertingTest.cs
new file mode 100644
index 0000000..a0385fd
--- /dev/null
+++ b/Livet.Test/StatefulModel/ConvertingTest.cs
@@ -0,0 +1,18 @@
+using Livet.StatefulModel;
+using NUnit.Framework;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Livet.NUnit.StatefulModel
+{
+ [TestFixture]
+ public class ConvertingTest
+ {
+ [Test]
+ public void ToReadOnlyTest()
+ {
+ var src = new ObservableSynchronizedCollection();
+ }
+ }
+}
diff --git a/Livet.Test/StatefulModel/FilteredObservableCollectionTest.cs b/Livet.Test/StatefulModel/FilteredObservableCollectionTest.cs
new file mode 100644
index 0000000..efe49ad
--- /dev/null
+++ b/Livet.Test/StatefulModel/FilteredObservableCollectionTest.cs
@@ -0,0 +1,50 @@
+using Livet.StatefulModel;
+using NUnit.Framework;
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Linq;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+
+namespace Livet.NUnit.StatefulModel
+{
+ [TestFixture]
+ public class FilteredObservableCollectionTest
+ {
+ private FilteredObservableCollection _target;
+ private List _collectionChanged;
+ [SetUp]
+ public void SetUp()
+ {
+ _target = new FilteredObservableCollection(x => !x.Contains("x"));
+ _collectionChanged = new List();
+ _target.CollectionChanged += (_, e) => _collectionChanged.Add(e);
+ }
+
+ [TearDown]
+ public void TearDown()
+ {
+ _target = null;
+ _collectionChanged = null;
+ }
+
+ [Test]
+ public void BasicUsage()
+ {
+ _target.Add("aaa");
+ _target.Add("axa");
+ _target.Add("bbb");
+ _target.Add("bxb");
+ _target.Is("aaa", "bbb");
+
+ _collectionChanged.SelectMany(x => x.NewItems.Cast())
+ .Is("aaa", "bbb");
+ _collectionChanged.Select(x => x.Action)
+ .Is(
+ NotifyCollectionChangedAction.Add,
+ NotifyCollectionChangedAction.Add
+ );
+ }
+ }
+}
diff --git a/Livet.Test/StatefulModel/ObservableSynchronizedCollectionTest.cs b/Livet.Test/StatefulModel/ObservableSynchronizedCollectionTest.cs
new file mode 100644
index 0000000..fe9b9e0
--- /dev/null
+++ b/Livet.Test/StatefulModel/ObservableSynchronizedCollectionTest.cs
@@ -0,0 +1,83 @@
+using Livet.StatefulModel;
+using NUnit.Framework;
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Linq;
+using System.Text;
+
+namespace Livet.NUnit.StatefulModel
+{
+ [TestFixture]
+ public class ObservableSynchronizedCollectionTest
+ {
+ private ObservableSynchronizedCollection _target;
+ private List _collectionChanged;
+ [SetUp]
+ public void SetUp()
+ {
+ _target = new ObservableSynchronizedCollection();
+ _collectionChanged = new List();
+ _target.CollectionChanged += (_, args) => _collectionChanged.Add(args);
+ }
+
+ [TearDown]
+ public void TearDown()
+ {
+ _target = null;
+ _collectionChanged = null;
+ }
+
+ [Test]
+ public void ConstructorTest()
+ {
+ var c = new ObservableSynchronizedCollection(new[] { "a", "b", "c" });
+ c.Count.Is(3);
+ }
+
+ [Test]
+ public void BasicUsage()
+ {
+ _target.Count.Is(0);
+
+ _target.Add("xxx");
+ _collectionChanged.Count.Is(1);
+ var collectionChangedEventArgs = _collectionChanged.First();
+ collectionChangedEventArgs.IsNotNull();
+ collectionChangedEventArgs.Action.Is(NotifyCollectionChangedAction.Add);
+ collectionChangedEventArgs.NewItems.Count.Is(1);
+ collectionChangedEventArgs.NewItems.Cast().First().Is("xxx");
+ }
+
+ [Test]
+ public void InsertTest()
+ {
+ _target.Add("1");
+ _target.Add("2");
+ _target.Insert(1, "xxx");
+
+ _target.Is("1", "xxx", "2");
+ _collectionChanged.SelectMany(x => x.NewItems.Cast())
+ .Is("1", "2", "xxx");
+ _collectionChanged.Select(x => x.Action)
+ .Is(NotifyCollectionChangedAction.Add, NotifyCollectionChangedAction.Add, NotifyCollectionChangedAction.Add);
+ }
+
+ [Test]
+ public void RemoveTest()
+ {
+ _target.Add("1");
+ _target.Add("2");
+
+ _target.Remove("1");
+ _target.Is("2");
+
+ _collectionChanged.Select(x => x.Action)
+ .Is(NotifyCollectionChangedAction.Add, NotifyCollectionChangedAction.Add, NotifyCollectionChangedAction.Remove);
+ _collectionChanged.SelectMany(x => x.NewItems?.Cast() ?? Enumerable.Empty())
+ .Is("1", "2");
+ _collectionChanged.Last().OldItems.Count.Is(1);
+ _collectionChanged.Last().OldItems.Cast().Is("1");
+ }
+ }
+}
diff --git a/Livet.Test/StatefulModel/SortedObservableCollectionTest.cs b/Livet.Test/StatefulModel/SortedObservableCollectionTest.cs
new file mode 100644
index 0000000..8751862
--- /dev/null
+++ b/Livet.Test/StatefulModel/SortedObservableCollectionTest.cs
@@ -0,0 +1,53 @@
+using Livet.StatefulModel;
+using NUnit.Framework;
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Linq;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+
+namespace Livet.NUnit.StatefulModel
+{
+ [TestFixture]
+ public class SortedObservableCollectionTest
+ {
+ private SortedObservableCollection<(int key, string value), int> _target;
+ private List _collectionChanged;
+
+ [SetUp]
+ public void SetUp()
+ {
+ _target = new SortedObservableCollection<(int key, string value), int>(x => x.key);
+ _collectionChanged = new List();
+ _target.CollectionChanged += (_, e) => _collectionChanged.Add(e);
+ }
+
+ [TearDown]
+ public void TearDown()
+ {
+ _target = null;
+ _collectionChanged = null;
+ }
+
+ [Test]
+ public void BasicUsage()
+ {
+ _target.Add((100, "max"));
+ _target.Add((0, "min"));
+ _target.Add((50, "mid"));
+
+ _target.Count.Is(3);
+ _target.Is(
+ (0, "min"),
+ (50, "mid"),
+ (100, "max")
+ );
+
+ _collectionChanged.Select(x => x.Action)
+ .Is(NotifyCollectionChangedAction.Add, NotifyCollectionChangedAction.Add, NotifyCollectionChangedAction.Add);
+ _collectionChanged.Select(x => x.NewStartingIndex)
+ .Is(0, 0, 1);
+ }
+ }
+}
diff --git a/Livet.Test/StatefulModel/SynchronizationContextCollectionTest.cs b/Livet.Test/StatefulModel/SynchronizationContextCollectionTest.cs
new file mode 100644
index 0000000..3c6f747
--- /dev/null
+++ b/Livet.Test/StatefulModel/SynchronizationContextCollectionTest.cs
@@ -0,0 +1,73 @@
+using Livet.StatefulModel;
+using NUnit.Framework;
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Linq;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+using System.Threading;
+
+namespace Livet.NUnit.StatefulModel
+{
+ [TestFixture]
+ public class SynchronizationContextCollectionTest
+ {
+ private TestSynchronizationContext _context;
+ private SynchronizationContextCollection _target;
+ private List _collectionChanged;
+
+ [SetUp]
+ public void SetUp()
+ {
+ _context = new TestSynchronizationContext();
+ _target = new SynchronizationContextCollection(_context);
+ _collectionChanged = new List();
+ _target.CollectionChanged += (_, e) => _collectionChanged.Add(e);
+ }
+
+ [TearDown]
+ public void TearDown()
+ {
+ _target = null;
+ _context = null;
+ }
+
+ [Test]
+ public void BasicUsage()
+ {
+ _target.Add("a");
+ _target.Add("b");
+ _target.Add("c");
+ _target.Remove("b");
+ _target[1] = "C";
+
+ _target.Is("a", "C");
+ _context.Count.Is(5);
+
+ _collectionChanged.Select(x => x.Action)
+ .Is(
+ NotifyCollectionChangedAction.Add,
+ NotifyCollectionChangedAction.Add,
+ NotifyCollectionChangedAction.Add,
+ NotifyCollectionChangedAction.Remove,
+ NotifyCollectionChangedAction.Replace
+ );
+ _collectionChanged.SelectMany(x => x.NewItems?.Cast() ?? Enumerable.Empty())
+ .Is("a", "b", "c", "C");
+ _collectionChanged[3].OldItems.Cast().Is("b");
+ _collectionChanged[4].Is(
+ x => x.Action == NotifyCollectionChangedAction.Replace && x.NewItems.Cast().ElementAt(0) == "C" && x.NewStartingIndex == 1);
+ }
+
+ class TestSynchronizationContext : SynchronizationContext
+ {
+ public int Count { get; set; }
+ public override void Post(SendOrPostCallback d, object state)
+ {
+ Count++;
+ d(state);
+ }
+ }
+ }
+}
diff --git a/Livet.props b/Livet.props
index a8e847f..ad041b8 100644
--- a/Livet.props
+++ b/Livet.props
@@ -1,6 +1,7 @@
- 3.2.0
+ 3.2.1
+ $(Version)-pre
diff --git a/Livet.sln b/Livet.sln
index 41bd836..7540095 100644
--- a/Livet.sln
+++ b/Livet.sln
@@ -79,7 +79,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LivetCask.Messaging", "Live
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LivetCask.Core", "LivetCask.Core\LivetCask.Core.csproj", "{08D2B468-ACB2-4917-ABE0-3587E6F174AB}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LivetCask.Collections", "LivetCask.Collections\LivetCask.Collections.csproj", "{5466DEAF-BF54-4F43-AB1D-75631251A8B5}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LivetCask.StatefulModel", "LivetCask.StatefulModel\LivetCask.StatefulModel.csproj", "{CA4BC629-774E-40FB-87D7-E3EC96E87744}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LivetCask.Collections", "LivetCask.Collections\LivetCask.Collections.csproj", "{A0BBA5C9-CB69-480E-96EC-9359214DDC5A}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LivetCask.Mvvm", "LivetCask.Mvvm\LivetCask.Mvvm.csproj", "{7BD47B69-E85C-40BF-B2B8-4EF1E3DACF9F}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
@@ -254,14 +258,30 @@ Global
{08D2B468-ACB2-4917-ABE0-3587E6F174AB}.Release|Any CPU.Build.0 = Release|Any CPU
{08D2B468-ACB2-4917-ABE0-3587E6F174AB}.Release|x86.ActiveCfg = Release|Any CPU
{08D2B468-ACB2-4917-ABE0-3587E6F174AB}.Release|x86.Build.0 = Release|Any CPU
- {5466DEAF-BF54-4F43-AB1D-75631251A8B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {5466DEAF-BF54-4F43-AB1D-75631251A8B5}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {5466DEAF-BF54-4F43-AB1D-75631251A8B5}.Debug|x86.ActiveCfg = Debug|Any CPU
- {5466DEAF-BF54-4F43-AB1D-75631251A8B5}.Debug|x86.Build.0 = Debug|Any CPU
- {5466DEAF-BF54-4F43-AB1D-75631251A8B5}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {5466DEAF-BF54-4F43-AB1D-75631251A8B5}.Release|Any CPU.Build.0 = Release|Any CPU
- {5466DEAF-BF54-4F43-AB1D-75631251A8B5}.Release|x86.ActiveCfg = Release|Any CPU
- {5466DEAF-BF54-4F43-AB1D-75631251A8B5}.Release|x86.Build.0 = Release|Any CPU
+ {CA4BC629-774E-40FB-87D7-E3EC96E87744}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CA4BC629-774E-40FB-87D7-E3EC96E87744}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CA4BC629-774E-40FB-87D7-E3EC96E87744}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {CA4BC629-774E-40FB-87D7-E3EC96E87744}.Debug|x86.Build.0 = Debug|Any CPU
+ {CA4BC629-774E-40FB-87D7-E3EC96E87744}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CA4BC629-774E-40FB-87D7-E3EC96E87744}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CA4BC629-774E-40FB-87D7-E3EC96E87744}.Release|x86.ActiveCfg = Release|Any CPU
+ {CA4BC629-774E-40FB-87D7-E3EC96E87744}.Release|x86.Build.0 = Release|Any CPU
+ {A0BBA5C9-CB69-480E-96EC-9359214DDC5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A0BBA5C9-CB69-480E-96EC-9359214DDC5A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A0BBA5C9-CB69-480E-96EC-9359214DDC5A}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A0BBA5C9-CB69-480E-96EC-9359214DDC5A}.Debug|x86.Build.0 = Debug|Any CPU
+ {A0BBA5C9-CB69-480E-96EC-9359214DDC5A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A0BBA5C9-CB69-480E-96EC-9359214DDC5A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A0BBA5C9-CB69-480E-96EC-9359214DDC5A}.Release|x86.ActiveCfg = Release|Any CPU
+ {A0BBA5C9-CB69-480E-96EC-9359214DDC5A}.Release|x86.Build.0 = Release|Any CPU
+ {7BD47B69-E85C-40BF-B2B8-4EF1E3DACF9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7BD47B69-E85C-40BF-B2B8-4EF1E3DACF9F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7BD47B69-E85C-40BF-B2B8-4EF1E3DACF9F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {7BD47B69-E85C-40BF-B2B8-4EF1E3DACF9F}.Debug|x86.Build.0 = Debug|Any CPU
+ {7BD47B69-E85C-40BF-B2B8-4EF1E3DACF9F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7BD47B69-E85C-40BF-B2B8-4EF1E3DACF9F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7BD47B69-E85C-40BF-B2B8-4EF1E3DACF9F}.Release|x86.ActiveCfg = Release|Any CPU
+ {7BD47B69-E85C-40BF-B2B8-4EF1E3DACF9F}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Livet.sln.DotSettings b/Livet.sln.DotSettings
deleted file mode 100644
index 337e5cf..0000000
--- a/Livet.sln.DotSettings
+++ /dev/null
@@ -1,157 +0,0 @@
-
- True
- F2FC8969-7230-4098-977F-9C6D78A89FA2
- SUGGESTION
- SUGGESTION
- SUGGESTION
- SUGGESTION
- SUGGESTION
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- WARNING
- SUGGESTION
- DO_NOT_SHOW
- DO_NOT_SHOW
- DO_NOT_SHOW
- PESSIMISTIC
- Built-in: Full Cleanup
- AccessorsWithBlockBody
- True
- RequiredForMultiline
- RequiredForMultiline
- RequiredForMultiline
- Required
- TOGETHER_SAME_LINE
- 1
- 1
- True
- 10001
- IF_OWNER_IS_SINGLE_LINE
- True
- True
- True
- True
- True
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
- True
\ No newline at end of file
diff --git a/LivetCask.Behaviors/LivetCask.Behaviors.csproj b/LivetCask.Behaviors/LivetCask.Behaviors.csproj
index 466ae2d..b9ce95b 100644
--- a/LivetCask.Behaviors/LivetCask.Behaviors.csproj
+++ b/LivetCask.Behaviors/LivetCask.Behaviors.csproj
@@ -23,7 +23,7 @@ This package is for useful behaviors for MVVM pattern.
$(Version)
$(Version)
- $(Version)
+ $(PackageVersion)
true
Livet.snk
diff --git a/LivetCask/DispatcherCollection.cs b/LivetCask.Collections/DispatcherCollection.cs
similarity index 100%
rename from LivetCask/DispatcherCollection.cs
rename to LivetCask.Collections/DispatcherCollection.cs
diff --git a/LivetCask/EventArgsFactory.cs b/LivetCask.Collections/EventArgsFactory.cs
similarity index 100%
rename from LivetCask/EventArgsFactory.cs
rename to LivetCask.Collections/EventArgsFactory.cs
diff --git a/LivetCask.Collections/LivetCask.Collections.csproj b/LivetCask.Collections/LivetCask.Collections.csproj
index 3c346f4..d7c0eb8 100644
--- a/LivetCask.Collections/LivetCask.Collections.csproj
+++ b/LivetCask.Collections/LivetCask.Collections.csproj
@@ -1,8 +1,8 @@
-
-
+
- netcoreapp3.0;net452;netstandard2.0
+ netcoreapp3.0;net452
+ true
Livet.Collections
Livet.Collections
LivetCask.Collections
@@ -10,8 +10,8 @@
Livet Project
Livet is the infrastructure of MVVM pattern on WPF.
-It supports .NET Framework 4.5.2 or later, .NET Core 3.0 and .NET Standard 2.0, and licensed as zlib/libpng.
-This package is for useful collections for MVVM pattern.
+It supports .NET Framework 4.5.2 or later and .NET Core 3.0, and licensed as zlib/libpng.
+This package is for basic collections for MVVM pattern.
Copyright (c) 2010-2019 Livet Project
license-en.txt
@@ -23,11 +23,18 @@ This package is for useful collections for MVVM pattern.
$(Version)
$(Version)
- $(Version)
+ $(PackageVersion)
true
Livet.snk
+
+
+
+
+
+
+
@@ -36,8 +43,9 @@ This package is for useful collections for MVVM pattern.
-
+
+
diff --git a/LivetCask.Collections/ObservableSynchronizedCollection.cs b/LivetCask.Collections/ObservableSynchronizedCollection.cs
index f1a6379..20677c4 100644
--- a/LivetCask.Collections/ObservableSynchronizedCollection.cs
+++ b/LivetCask.Collections/ObservableSynchronizedCollection.cs
@@ -4,135 +4,262 @@
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
+using System.Runtime.CompilerServices;
using System.Threading;
+using Livet.Annotations;
-namespace Livet.Collections
+namespace Livet
{
- public sealed class ObservableSynchronizedCollection :ICollection, IList, IReadOnlyList, ISynchronizableNotifyChangedCollection
+ ///
+ /// スレッドセーフな変更通知コレクションです。
+ ///
+ /// コレクションアイテムの型
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")]
+ [Serializable]
+ public class ObservableSynchronizedCollection : IList, ICollection, INotifyCollectionChanged,
+ INotifyPropertyChanged, IReadOnlyList
{
- private readonly IList _list;
- private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
+ private const string ItemsString = "Item[]";
+ [NotNull] private readonly IList _list;
- public ObservableSynchronizedCollection() : this(Enumerable.Empty()) { }
+ [NonSerialized] [NotNull] private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
- public ObservableSynchronizedCollection(IEnumerable source)
+ ///
+ /// デフォルトコンストラクタ
+ ///
+ public ObservableSynchronizedCollection()
+ {
+ _list = new List();
+ }
+
+ ///
+ /// コンストラクタ
+ ///
+ /// 初期値となるソース
+ public ObservableSynchronizedCollection([NotNull] IEnumerable source)
{
if (source == null) throw new ArgumentNullException(nameof(source));
_list = new List(source);
- Synchronizer = new Synchronizer(this);
}
+ ///
+ /// 全体を互換性のある1次元の配列にコピーします。コピー操作は、コピー先の配列の指定したインデックスから始まります。
+ ///
+ /// コピー先の配列
+ /// コピー先の配列のどこからコピー操作をするかのインデックス
+ public void CopyTo(Array array, int index)
+ {
+ CopyTo(array.Cast().ToArray(), index);
+ }
+
+ ///
+ /// このコレクションがスレッドセーフであるかどうかを取得します。(常にtrueを返します)
+ ///
+ public bool IsSynchronized
+ {
+ get { return true; }
+ }
+
+ ///
+ /// このコレクションへのスレッドセーフなアクセスに使用できる同期オブジェクトを返します。
+ ///
+ [field: NonSerialized]
+ public object SyncRoot { get; } = new object();
+
+ ///
+ /// 指定したオブジェクトを検索し、最初に見つかった位置の 0 から始まるインデックスを返します。
+ ///
+ /// 検索するオブジェクト
+ /// 最初に見つかった位置のインデックス
public int IndexOf(T item)
{
return ReadWithLockAction(() => _list.IndexOf(item));
}
+ ///
+ /// 指定したインデックスの位置に要素を挿入します。
+ ///
+ /// 指定するインデックス
+ /// 挿入するオブジェクト
public void Insert(int index, T item)
{
ReadAndWriteWithLockAction(() => _list.Insert(index, item),
() =>
{
- PropertyChanged?.Invoke(this, EventArguments.CountPropertyChangedEventArgs);
- PropertyChanged?.Invoke(this, EventArguments.ItemPropertyChangedEventArgs);
- CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
+ OnPropertyChanged(nameof(Count));
+ OnPropertyChanged(ItemsString);
+ OnCollectionChanged(
+ new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
});
}
+ ///
+ /// 指定したインデックスにある要素を削除します。
+ ///
+ /// 指定するインデックス
public void RemoveAt(int index)
{
ReadAndWriteWithLockAction(() => _list[index],
removeItem => _list.RemoveAt(index),
removeItem =>
{
- PropertyChanged?.Invoke(this, EventArguments.CountPropertyChangedEventArgs);
- PropertyChanged?.Invoke(this, EventArguments.ItemPropertyChangedEventArgs);
- CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removeItem, index));
+ OnPropertyChanged(nameof(Count));
+ OnPropertyChanged(ItemsString);
+ OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove,
+ removeItem, index));
});
}
public T this[int index]
{
- get
- {
- return ReadWithLockAction(() => _list[index]);
- }
+ get { return ReadWithLockAction(() => _list[index]); }
set
{
ReadAndWriteWithLockAction(() => _list[index],
+ oldItem => { _list[index] = value; },
oldItem =>
{
- _list[index] = value;
- },
- oldItem =>
- {
- PropertyChanged?.Invoke(this, EventArguments.ItemPropertyChangedEventArgs);
- CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, _list[index], oldItem, index));
+ OnPropertyChanged(ItemsString);
+ OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace,
+ _list[index], oldItem, index));
});
}
}
+ ///
+ /// 末尾にオブジェクトを追加します。
+ ///
+ /// 追加するオブジェクト
public void Add(T item)
{
ReadAndWriteWithLockAction(() => _list.Add(item),
() =>
{
- PropertyChanged?.Invoke(this, EventArguments.CountPropertyChangedEventArgs);
- PropertyChanged?.Invoke(this, EventArguments.ItemPropertyChangedEventArgs);
- CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, _list.Count - 1));
+ OnPropertyChanged(nameof(Count));
+ OnPropertyChanged(ItemsString);
+ OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item,
+ _list.Count - 1));
});
}
+ ///
+ /// すべての要素を削除します。
+ ///
public void Clear()
{
ReadAndWriteWithLockAction(() => _list.Count,
- count =>
- {
- if (count != 0)
+ count =>
{
+ if (count == 0) return;
+
_list.Clear();
- }
- },
- count =>
- {
- if (count != 0)
+ },
+ count =>
{
- PropertyChanged?.Invoke(this, EventArguments.CountPropertyChangedEventArgs);
- PropertyChanged?.Invoke(this, EventArguments.ItemPropertyChangedEventArgs);
- CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
- }
- });
+ if (count == 0) return;
+
+ OnPropertyChanged(nameof(Count));
+ OnPropertyChanged(ItemsString);
+ OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
+ });
}
- public bool Contains(T item) => ReadWithLockAction(() => _list.Contains(item));
+ ///
+ /// ある要素がこのコレクションに含まれているかどうかを判断します。
+ ///
+ /// コレクションに含まれているか判断したい要素
+ /// このコレクションに含まれているかどうか
+ public bool Contains(T item)
+ {
+ return ReadWithLockAction(() => _list.Contains(item));
+ }
- public void CopyTo(T[] array, int arrayIndex) => ReadWithLockAction(() => _list.CopyTo(array, arrayIndex));
+ ///
+ /// 全体を互換性のある1次元の配列にコピーします。コピー操作は、コピー先の配列の指定したインデックスから始まります。
+ ///
+ /// コピー先の配列
+ /// コピー先の配列のどこからコピー操作をするかのインデックス
+ public void CopyTo(T[] array, int arrayIndex)
+ {
+ ReadWithLockAction(() => _list.CopyTo(array, arrayIndex));
+ }
- public int Count => ReadWithLockAction(() => _list.Count);
+ ///
+ /// 実際に格納されている要素の数を取得します。
+ ///
+ public int Count
+ {
+ get { return ReadWithLockAction(() => _list.Count); }
+ }
- public bool IsReadOnly => _list.IsReadOnly;
+ ///
+ /// このコレクションが読み取り専用かどうかを取得します。
+ ///
+ public bool IsReadOnly
+ {
+ get { return _list.IsReadOnly; }
+ }
+ ///
+ /// 最初に見つかった特定のオブジェクトを削除します。
+ ///
+ /// 削除したいオブジェクト
+ /// 削除できたかどうか
public bool Remove(T item)
{
- bool result = false;
+ var result = false;
ReadAndWriteWithLockAction(() => _list.IndexOf(item),
+ index => { result = _list.Remove(item); },
index =>
{
- result = _list.Remove(item);
- },
- index =>
- {
- if (result)
- {
- PropertyChanged?.Invoke(this, EventArguments.CountPropertyChangedEventArgs);
- PropertyChanged?.Invoke(this, EventArguments.ItemPropertyChangedEventArgs);
- CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index));
- }
+ if (!result) return;
+
+ OnPropertyChanged(nameof(Count));
+ OnPropertyChanged(ItemsString);
+ OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove,
+ item, index));
});
return result;
}
+ ///
+ /// 反復処理するためのスナップショットの列挙子を返します。
+ ///
+ /// 列挙子
+ public IEnumerator GetEnumerator()
+ {
+ return ReadWithLockAction(() => ((IEnumerable) _list.ToArray()).GetEnumerator())
+ ?? Enumerable.Empty().GetEnumerator();
+ }
+
+ ///
+ /// 反復処理するためのスナップショットの列挙子を返します。
+ ///
+ /// 列挙子
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ ///
+ /// コレクションが変更された際に発生するイベントです。
+ ///
+ [field: NonSerialized]
+ public event NotifyCollectionChangedEventHandler CollectionChanged;
+
+ ///
+ /// プロパティが変更された際に発生するイベントです。
+ ///
+ [field: NonSerialized]
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ ///
+ /// 指定されたインデックスの要素を指定されたインデックスに移動します。
+ ///
+ /// 移動したい要素のインデックス
+ /// 移動先のインデックス
public void Move(int oldIndex, int newIndex)
{
ReadAndWriteWithLockAction(() => _list[oldIndex],
@@ -143,185 +270,103 @@ public void Move(int oldIndex, int newIndex)
},
item =>
{
- PropertyChanged?.Invoke(this, EventArguments.ItemPropertyChangedEventArgs);
- CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, item, newIndex, oldIndex));
+ OnPropertyChanged(ItemsString);
+ OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, item,
+ newIndex, oldIndex));
});
}
- public IEnumerator GetEnumerator() => ReadWithLockAction(() => ((IEnumerable)_list.ToArray()).GetEnumerator());
+ ///
+ /// CollectionChangedイベントを発生させます。
+ ///
+ /// NotifyCollectionChangedEventArgs
+ protected void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
+ {
+ var threadSafeHandler = Interlocked.CompareExchange(ref CollectionChanged, null, null);
- IEnumerator IEnumerable.GetEnumerator() => ReadWithLockAction(() => ((IEnumerable)_list.ToArray()).GetEnumerator());
+ threadSafeHandler?.Invoke(this, args);
+ }
+ ///
+ /// PropertyChangedイベントを発生させます。
+ ///
+ /// 変更されたプロパティの名前
+ [NotifyPropertyChangedInvocator]
+ protected void OnPropertyChanged([CallerMemberName] [CanBeNull] string propertyName = "")
+ {
+ var threadSafeHandler = Interlocked.CompareExchange(ref PropertyChanged, null, null);
+ threadSafeHandler?.Invoke(this, EventArgsFactory.GetPropertyChangedEventArgs(propertyName));
+ }
- private void ReadWithLockAction(Action readAction)
+ private void ReadWithLockAction([NotNull] Action readAction)
{
+ if (readAction == null) throw new ArgumentNullException(nameof(readAction));
+
if (!_lock.IsReadLockHeld)
{
_lock.EnterReadLock();
- try
- {
- readAction();
- }
- finally
- {
- _lock.ExitReadLock();
- }
+ try { readAction(); }
+ finally { _lock.ExitReadLock(); }
}
else
- {
readAction();
- }
}
- private TResult ReadWithLockAction(Func readAction)
+ private TResult ReadWithLockAction([NotNull] Func readAction)
{
- if (!_lock.IsReadLockHeld)
- {
- _lock.EnterReadLock();
- try
- {
- return readAction();
- }
- finally
- {
- _lock.ExitReadLock();
- }
- }
-
- return readAction();
- }
+ if (readAction == null) throw new ArgumentNullException(nameof(readAction));
- private void ReadAndWriteWithLockAction(Action writeAction, Action readAfterWriteAction)
- {
- lock (Synchronizer.LockObject)
- {
- _lock.EnterUpgradeableReadLock();
- try
- {
- _lock.EnterWriteLock();
- try
- {
- writeAction();
- }
- finally
- {
- _lock.ExitWriteLock();
- }
+ if (_lock.IsReadLockHeld) return readAction();
- _lock.EnterReadLock();
+ _lock.EnterReadLock();
- try
- {
- readAfterWriteAction();
- }
- finally
- {
- _lock.ExitReadLock();
- }
- }
- finally
- {
- _lock.ExitUpgradeableReadLock();
- }
- }
+ try { return readAction(); }
+ finally { _lock.ExitReadLock(); }
}
- private void ReadAndWriteWithLockAction(Func readBeforeWriteAction, Action writeAction, Action readAfterWriteAction)
+ private void ReadAndWriteWithLockAction([NotNull] Action writeAction, [NotNull] Action readAfterWriteAction)
{
- lock (Synchronizer.LockObject)
- {
- _lock.EnterUpgradeableReadLock();
- try
- {
- TResult readActionResult = readBeforeWriteAction();
-
- _lock.EnterWriteLock();
+ if (writeAction == null) throw new ArgumentNullException(nameof(writeAction));
+ if (readAfterWriteAction == null) throw new ArgumentNullException(nameof(readAfterWriteAction));
- try
- {
- writeAction(readActionResult);
- }
- finally
- {
- _lock.ExitWriteLock();
- }
+ _lock.EnterUpgradeableReadLock();
+ try
+ {
+ _lock.EnterWriteLock();
+ try { writeAction(); }
+ finally { _lock.ExitWriteLock(); }
- _lock.EnterReadLock();
+ _lock.EnterReadLock();
- try
- {
- readAfterWriteAction(readActionResult);
- }
- finally
- {
- _lock.ExitReadLock();
- }
- }
- finally
- {
- _lock.ExitUpgradeableReadLock();
- }
+ try { readAfterWriteAction(); }
+ finally { _lock.ExitReadLock(); }
}
-
+ finally { _lock.ExitUpgradeableReadLock(); }
}
- public event NotifyCollectionChangedEventHandler CollectionChanged;
-
- public event PropertyChangedEventHandler PropertyChanged;
-
- public Synchronizer Synchronizer{get;}
+ private void ReadAndWriteWithLockAction([NotNull] Func readBeforeWriteAction,
+ [NotNull] Action writeAction, [NotNull] Action readAfterWriteAction)
+ {
+ if (readBeforeWriteAction == null) throw new ArgumentNullException(nameof(readBeforeWriteAction));
+ if (writeAction == null) throw new ArgumentNullException(nameof(writeAction));
+ if (readAfterWriteAction == null) throw new ArgumentNullException(nameof(readAfterWriteAction));
- public void Dispose() => Synchronizer.Dispose();
+ _lock.EnterUpgradeableReadLock();
+ try
+ {
+ var readActionResult = readBeforeWriteAction();
- #region ICollection(non-generic) support
- void ICollection.CopyTo(Array array, int index) => CopyTo(array.Cast().ToArray(), index);
- bool ICollection.IsSynchronized { get; } = false;
- object ICollection.SyncRoot { get; } = new object();
- #endregion
+ _lock.EnterWriteLock();
- #region IList(non-generic) support
- object IList.this[int index]
- {
- get { return _list[index]; }
- set { this[index] = (T)value; }
- }
- void IList.Remove(object value) => Remove((T)value);
- bool IList.IsFixedSize { get; } = false;
- int IList.Add(object value)
- {
- Add((T)value);
- return _list.Count - 1;
- }
- bool IList.Contains(object value) => Contains((T)value);
- int IList.IndexOf(object value) => IndexOf((T)value);
- void IList.Insert(int index, object value) => Insert(index, (T)value);
- #endregion
- }
+ try { writeAction(readActionResult); }
+ finally { _lock.ExitWriteLock(); }
- public static class ObservableSynchronizedCollectionExtensions
- {
- public static ObservableSynchronizedCollection ToSyncedObservableSynchronizedCollection(
- this ISynchronizableNotifyChangedCollection source) => ToSyncedObservableSynchronizedCollection(source, _ => _);
+ _lock.EnterReadLock();
- public static ObservableSynchronizedCollection ToSyncedObservableSynchronizedCollection(
- this ISynchronizableNotifyChangedCollection source,
- Func converter)
- {
- lock (source.Synchronizer.LockObject)
- {
- var result = new ObservableSynchronizedCollection();
- foreach (var item in source)
- {
- result.Add(converter(item));
- }
-
- var collectionChangedListener =
- SynchronizableNotifyChangedCollectionHelper.CreateSynchronizableCollectionChangedEventListener(
- source, result,
- converter);
- result.Synchronizer.EventListeners.Add(collectionChangedListener);
- return result;
+ try { readAfterWriteAction(readActionResult); }
+ finally { _lock.ExitReadLock(); }
}
+ finally { _lock.ExitUpgradeableReadLock(); }
}
}
-}
+}
\ No newline at end of file
diff --git a/LivetCask/ReadOnlyDispatcherCollection.cs b/LivetCask.Collections/ReadOnlyDispatcherCollection.cs
similarity index 100%
rename from LivetCask/ReadOnlyDispatcherCollection.cs
rename to LivetCask.Collections/ReadOnlyDispatcherCollection.cs
diff --git a/LivetCask/ViewModelHelper.cs b/LivetCask.Collections/ViewModelHelper.cs
similarity index 100%
rename from LivetCask/ViewModelHelper.cs
rename to LivetCask.Collections/ViewModelHelper.cs
diff --git a/LivetCask.Converters/LivetCask.Converters.csproj b/LivetCask.Converters/LivetCask.Converters.csproj
index 1a79051..96bc60e 100644
--- a/LivetCask.Converters/LivetCask.Converters.csproj
+++ b/LivetCask.Converters/LivetCask.Converters.csproj
@@ -24,7 +24,7 @@ This package is for useful converters for MVVM pattern.
$(Version)
$(Version)
- $(Version)
+ $(PackageVersion)
true
Livet.snk
diff --git a/LivetCask.Core/LivetCask.Core.csproj b/LivetCask.Core/LivetCask.Core.csproj
index e8beea5..f038995 100644
--- a/LivetCask.Core/LivetCask.Core.csproj
+++ b/LivetCask.Core/LivetCask.Core.csproj
@@ -2,8 +2,7 @@
- netcoreapp3.0;net452;netstandard2.0
- true
+ net452;netstandard2.0
Livet
Livet.Core
LivetCask.Core
@@ -24,7 +23,7 @@ This package is for core features for Livet.
$(Version)
$(Version)
- $(Version)
+ $(PackageVersion)
true
Livet.snk
diff --git a/LivetCask.EventListeners/LivetCask.EventListeners.csproj b/LivetCask.EventListeners/LivetCask.EventListeners.csproj
index 50b3aa3..693a295 100644
--- a/LivetCask.EventListeners/LivetCask.EventListeners.csproj
+++ b/LivetCask.EventListeners/LivetCask.EventListeners.csproj
@@ -2,8 +2,7 @@
- netcoreapp3.0;net452;netstandard2.0
- true
+ net452;netstandard2.0
Livet.EventListeners
Livet.EventListeners
LivetCask.EventListeners
@@ -24,7 +23,7 @@ This package is for useful event listeners using weak event pattern for MVVM pat
$(Version)
$(Version)
- $(Version)
+ $(PackageVersion)
true
Livet.snk
diff --git a/LivetCask.Extensions/LivetCask.Extensions.csproj b/LivetCask.Extensions/LivetCask.Extensions.csproj
index 4c9178a..f928d42 100644
--- a/LivetCask.Extensions/LivetCask.Extensions.csproj
+++ b/LivetCask.Extensions/LivetCask.Extensions.csproj
@@ -23,7 +23,7 @@
$(Version)
$(Version)
- $(Version)
+ $(PackageVersion)
true
Livet.snk
@@ -40,14 +40,11 @@
-
-
-
-
+
-
+
diff --git a/LivetCask.Extensions/XmlDefinitions.cs b/LivetCask.Extensions/XmlDefinitions.cs
new file mode 100644
index 0000000..09de877
--- /dev/null
+++ b/LivetCask.Extensions/XmlDefinitions.cs
@@ -0,0 +1,4 @@
+using System.Windows.Markup;
+
+[assembly: XmlnsDefinition("http://schemas.livet-mvvm.net/2011/wpf", "Livet.Messaging.IO")]
+[assembly: XmlnsDefinition("http://schemas.livet-mvvm.net/2011/wpf", "Livet.Behaviors.Messaging.IO")]
diff --git a/LivetCask.Messaging/LivetCask.Messaging.csproj b/LivetCask.Messaging/LivetCask.Messaging.csproj
index 225fb9b..21e5014 100644
--- a/LivetCask.Messaging/LivetCask.Messaging.csproj
+++ b/LivetCask.Messaging/LivetCask.Messaging.csproj
@@ -24,7 +24,7 @@ This package is for useful messaging features between View layer and ViewModel l
$(Version)
$(Version)
- $(Version)
+ $(PackageVersion)
true
Livet.snk
diff --git a/LivetCask/Commands/Command.cs b/LivetCask.Mvvm/Commands/Command.cs
similarity index 100%
rename from LivetCask/Commands/Command.cs
rename to LivetCask.Mvvm/Commands/Command.cs
diff --git a/LivetCask/Commands/ListenerCommand.cs b/LivetCask.Mvvm/Commands/ListenerCommand.cs
similarity index 100%
rename from LivetCask/Commands/ListenerCommand.cs
rename to LivetCask.Mvvm/Commands/ListenerCommand.cs
diff --git a/LivetCask/Commands/ViewModelCommand.cs b/LivetCask.Mvvm/Commands/ViewModelCommand.cs
similarity index 100%
rename from LivetCask/Commands/ViewModelCommand.cs
rename to LivetCask.Mvvm/Commands/ViewModelCommand.cs
diff --git a/LivetCask/DispatcherHelper.cs b/LivetCask.Mvvm/DispatcherHelper.cs
similarity index 100%
rename from LivetCask/DispatcherHelper.cs
rename to LivetCask.Mvvm/DispatcherHelper.cs
diff --git a/LivetCask.Mvvm/EventArgsFactory.cs b/LivetCask.Mvvm/EventArgsFactory.cs
new file mode 100644
index 0000000..ee19146
--- /dev/null
+++ b/LivetCask.Mvvm/EventArgsFactory.cs
@@ -0,0 +1,18 @@
+using System.Collections.Concurrent;
+using System.ComponentModel;
+using Livet.Annotations;
+
+namespace Livet
+{
+ internal static class EventArgsFactory
+ {
+ [NotNull] private static readonly ConcurrentDictionary
+ PropertyChangedEventArgsDictionary = new ConcurrentDictionary();
+
+ public static PropertyChangedEventArgs GetPropertyChangedEventArgs([CanBeNull] string propertyName)
+ {
+ return PropertyChangedEventArgsDictionary.GetOrAdd(propertyName ?? string.Empty,
+ name => new PropertyChangedEventArgs(name));
+ }
+ }
+}
\ No newline at end of file
diff --git a/LivetCask.Mvvm/Livet.snk b/LivetCask.Mvvm/Livet.snk
new file mode 100644
index 0000000..935d5e7
Binary files /dev/null and b/LivetCask.Mvvm/Livet.snk differ
diff --git a/LivetCask.Mvvm/LivetCask.Mvvm.csproj b/LivetCask.Mvvm/LivetCask.Mvvm.csproj
new file mode 100644
index 0000000..e78b68a
--- /dev/null
+++ b/LivetCask.Mvvm/LivetCask.Mvvm.csproj
@@ -0,0 +1,53 @@
+
+
+
+ netcoreapp3.0;net452
+ true
+ Livet
+ Livet.Mvvm
+ LivetCask.Mvvm
+ true
+ Livet Project
+ Livet is the infrastructure of MVVM pattern on WPF.
+It supports .NET Framework 4.5.2 or later, .NET Core 3.0, and licensed as zlib/libpng.
+
+ Copyright (c) 2010-2019 Livet Project
+ license-en.txt
+ https://github.com/runceel/Livet
+ https://raw.githubusercontent.com/ugaya40/Livet/master/Images/Livet.png
+ https://github.com/runceel/Livet
+ git
+ MVVM WPF
+
+ $(Version)
+ $(Version)
+ $(PackageVersion)
+ true
+ Livet.snk
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+
+
+
+
+
+
+
+
+
+
diff --git a/LivetCask/NotificationObject.cs b/LivetCask.Mvvm/NotificationObject.cs
similarity index 100%
rename from LivetCask/NotificationObject.cs
rename to LivetCask.Mvvm/NotificationObject.cs
diff --git a/LivetCask/ViewModel.cs b/LivetCask.Mvvm/ViewModel.cs
similarity index 100%
rename from LivetCask/ViewModel.cs
rename to LivetCask.Mvvm/ViewModel.cs
diff --git a/LivetCask.Mvvm/XmlDefinitions.cs b/LivetCask.Mvvm/XmlDefinitions.cs
new file mode 100644
index 0000000..44966ac
--- /dev/null
+++ b/LivetCask.Mvvm/XmlDefinitions.cs
@@ -0,0 +1,4 @@
+using System.Windows.Markup;
+
+[assembly: XmlnsDefinition("http://schemas.livet-mvvm.net/2011/wpf", "Livet")]
+[assembly: XmlnsDefinition("http://schemas.livet-mvvm.net/2011/wpf", "Livet.Commands")]
diff --git a/LivetCask.Collections/AnonymousComparer.cs b/LivetCask.StatefulModel/AnonymousComparer.cs
similarity index 92%
rename from LivetCask.Collections/AnonymousComparer.cs
rename to LivetCask.StatefulModel/AnonymousComparer.cs
index 47cdfe7..e321203 100644
--- a/LivetCask.Collections/AnonymousComparer.cs
+++ b/LivetCask.StatefulModel/AnonymousComparer.cs
@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
-namespace Livet.Collections
+namespace Livet.StatefulModel
{
public class AnonymousComparer : IComparer
{
diff --git a/LivetCask.Collections/AnonymousDisposable.cs b/LivetCask.StatefulModel/AnonymousDisposable.cs
similarity index 95%
rename from LivetCask.Collections/AnonymousDisposable.cs
rename to LivetCask.StatefulModel/AnonymousDisposable.cs
index c4b79bc..4f0c030 100644
--- a/LivetCask.Collections/AnonymousDisposable.cs
+++ b/LivetCask.StatefulModel/AnonymousDisposable.cs
@@ -1,6 +1,6 @@
using System;
-namespace Livet.Collections
+namespace Livet.StatefulModel
{
public class AnonymousDisposable : IDisposable
{
diff --git a/LivetCask.Collections/AnonymousSynchronizationContext.cs b/LivetCask.StatefulModel/AnonymousSynchronizationContext.cs
similarity index 98%
rename from LivetCask.Collections/AnonymousSynchronizationContext.cs
rename to LivetCask.StatefulModel/AnonymousSynchronizationContext.cs
index 9901624..5c24c39 100644
--- a/LivetCask.Collections/AnonymousSynchronizationContext.cs
+++ b/LivetCask.StatefulModel/AnonymousSynchronizationContext.cs
@@ -1,7 +1,7 @@
using System;
using System.Threading;
-namespace Livet.Collections
+namespace Livet.StatefulModel
{
public class AnonymousSynchronizationContext : SynchronizationContext
{
diff --git a/LivetCask.Collections/FilteredObservableCollection.cs b/LivetCask.StatefulModel/FilteredObservableCollection.cs
similarity index 99%
rename from LivetCask.Collections/FilteredObservableCollection.cs
rename to LivetCask.StatefulModel/FilteredObservableCollection.cs
index 976b2fb..3943905 100644
--- a/LivetCask.Collections/FilteredObservableCollection.cs
+++ b/LivetCask.StatefulModel/FilteredObservableCollection.cs
@@ -3,7 +3,7 @@
using System.Linq;
using System.Reflection;
-namespace Livet.Collections
+namespace Livet.StatefulModel
{
public sealed class FilteredObservableCollection : NotifyChangedCollection, ISynchronizableNotifyChangedCollection
{
diff --git a/LivetCask.Collections/ISynchronizableNotifyChangedCollection.cs b/LivetCask.StatefulModel/ISynchronizableNotifyChangedCollection.cs
similarity index 99%
rename from LivetCask.Collections/ISynchronizableNotifyChangedCollection.cs
rename to LivetCask.StatefulModel/ISynchronizableNotifyChangedCollection.cs
index d506a54..ca30cfc 100644
--- a/LivetCask.Collections/ISynchronizableNotifyChangedCollection.cs
+++ b/LivetCask.StatefulModel/ISynchronizableNotifyChangedCollection.cs
@@ -5,7 +5,7 @@
using System.Reflection;
using Livet.EventListeners;
-namespace Livet.Collections
+namespace Livet.StatefulModel
{
public interface ISynchronizableNotifyChangedCollection : INotifyCollectionChanged, INotifyPropertyChanged,
IDisposable
diff --git a/LivetCask.StatefulModel/Livet.snk b/LivetCask.StatefulModel/Livet.snk
new file mode 100644
index 0000000..935d5e7
Binary files /dev/null and b/LivetCask.StatefulModel/Livet.snk differ
diff --git a/LivetCask.StatefulModel/LivetCask.StatefulModel.csproj b/LivetCask.StatefulModel/LivetCask.StatefulModel.csproj
new file mode 100644
index 0000000..c44ece0
--- /dev/null
+++ b/LivetCask.StatefulModel/LivetCask.StatefulModel.csproj
@@ -0,0 +1,43 @@
+
+
+
+
+ net452;netstandard2.0
+ Livet.StatefulModel
+ Livet.StatefulModel
+ LivetCask.StatefulModel
+ true
+ Livet Project
+
+ Livet is the infrastructure of MVVM pattern on WPF.
+It supports .NET Framework 4.5.2 or later, .NET Core 3.0 and .NET Standard 2.0, and licensed as zlib/libpng.
+This package is for useful collections for MVVM pattern.
+
+ Copyright (c) 2010-2019 Livet Project
+ license-en.txt
+ https://github.com/runceel/Livet
+ https://raw.githubusercontent.com/ugaya40/Livet/master/Images/Livet.png
+ https://github.com/runceel/Livet
+ git
+ MVVM WPF
+
+ $(Version)
+ $(Version)
+ $(PackageVersion)
+ true
+ Livet.snk
+
+
+
+
+
+ True
+
+
+
+
+
+
+
+
+
diff --git a/LivetCask.Collections/NotifyChangedCollection.cs b/LivetCask.StatefulModel/NotifyChangedCollection.cs
similarity index 99%
rename from LivetCask.Collections/NotifyChangedCollection.cs
rename to LivetCask.StatefulModel/NotifyChangedCollection.cs
index 87fc4e1..49e0392 100644
--- a/LivetCask.Collections/NotifyChangedCollection.cs
+++ b/LivetCask.StatefulModel/NotifyChangedCollection.cs
@@ -8,7 +8,7 @@
using System.Text;
using System.Threading.Tasks;
-namespace Livet.Collections
+namespace Livet.StatefulModel
{
internal static class EventArguments
{
diff --git a/LivetCask.StatefulModel/ObservableSynchronizedCollection.cs b/LivetCask.StatefulModel/ObservableSynchronizedCollection.cs
new file mode 100644
index 0000000..7a52921
--- /dev/null
+++ b/LivetCask.StatefulModel/ObservableSynchronizedCollection.cs
@@ -0,0 +1,327 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using System.Linq;
+using System.Threading;
+
+namespace Livet.StatefulModel
+{
+ public sealed class ObservableSynchronizedCollection :ICollection, IList, IReadOnlyList, ISynchronizableNotifyChangedCollection
+ {
+ private readonly IList _list;
+ private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
+
+ public ObservableSynchronizedCollection() : this(Enumerable.Empty()) { }
+
+ public ObservableSynchronizedCollection(IEnumerable source)
+ {
+ if (source == null) throw new ArgumentNullException(nameof(source));
+ _list = new List(source);
+ Synchronizer = new Synchronizer(this);
+ }
+
+ public int IndexOf(T item)
+ {
+ return ReadWithLockAction(() => _list.IndexOf(item));
+ }
+
+ public void Insert(int index, T item)
+ {
+ ReadAndWriteWithLockAction(() => _list.Insert(index, item),
+ () =>
+ {
+ PropertyChanged?.Invoke(this, EventArguments.CountPropertyChangedEventArgs);
+ PropertyChanged?.Invoke(this, EventArguments.ItemPropertyChangedEventArgs);
+ CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
+ });
+ }
+
+ public void RemoveAt(int index)
+ {
+ ReadAndWriteWithLockAction(() => _list[index],
+ removeItem => _list.RemoveAt(index),
+ removeItem =>
+ {
+ PropertyChanged?.Invoke(this, EventArguments.CountPropertyChangedEventArgs);
+ PropertyChanged?.Invoke(this, EventArguments.ItemPropertyChangedEventArgs);
+ CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removeItem, index));
+ });
+ }
+
+ public T this[int index]
+ {
+ get
+ {
+ return ReadWithLockAction(() => _list[index]);
+ }
+ set
+ {
+ ReadAndWriteWithLockAction(() => _list[index],
+ oldItem =>
+ {
+ _list[index] = value;
+ },
+ oldItem =>
+ {
+ PropertyChanged?.Invoke(this, EventArguments.ItemPropertyChangedEventArgs);
+ CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, _list[index], oldItem, index));
+ });
+ }
+ }
+
+ public void Add(T item)
+ {
+ ReadAndWriteWithLockAction(() => _list.Add(item),
+ () =>
+ {
+ PropertyChanged?.Invoke(this, EventArguments.CountPropertyChangedEventArgs);
+ PropertyChanged?.Invoke(this, EventArguments.ItemPropertyChangedEventArgs);
+ CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, _list.Count - 1));
+ });
+ }
+
+ public void Clear()
+ {
+ ReadAndWriteWithLockAction(() => _list.Count,
+ count =>
+ {
+ if (count != 0)
+ {
+ _list.Clear();
+ }
+ },
+ count =>
+ {
+ if (count != 0)
+ {
+ PropertyChanged?.Invoke(this, EventArguments.CountPropertyChangedEventArgs);
+ PropertyChanged?.Invoke(this, EventArguments.ItemPropertyChangedEventArgs);
+ CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
+ }
+ });
+ }
+
+ public bool Contains(T item) => ReadWithLockAction(() => _list.Contains(item));
+
+ public void CopyTo(T[] array, int arrayIndex) => ReadWithLockAction(() => _list.CopyTo(array, arrayIndex));
+
+ public int Count => ReadWithLockAction(() => _list.Count);
+
+ public bool IsReadOnly => _list.IsReadOnly;
+
+ public bool Remove(T item)
+ {
+ bool result = false;
+
+ ReadAndWriteWithLockAction(() => _list.IndexOf(item),
+ index =>
+ {
+ result = _list.Remove(item);
+ },
+ index =>
+ {
+ if (result)
+ {
+ PropertyChanged?.Invoke(this, EventArguments.CountPropertyChangedEventArgs);
+ PropertyChanged?.Invoke(this, EventArguments.ItemPropertyChangedEventArgs);
+ CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index));
+ }
+ });
+
+ return result;
+ }
+
+ public void Move(int oldIndex, int newIndex)
+ {
+ ReadAndWriteWithLockAction(() => _list[oldIndex],
+ item =>
+ {
+ _list.RemoveAt(oldIndex);
+ _list.Insert(newIndex, item);
+ },
+ item =>
+ {
+ PropertyChanged?.Invoke(this, EventArguments.ItemPropertyChangedEventArgs);
+ CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, item, newIndex, oldIndex));
+ });
+ }
+
+ public IEnumerator GetEnumerator() => ReadWithLockAction(() => ((IEnumerable)_list.ToArray()).GetEnumerator());
+
+ IEnumerator IEnumerable.GetEnumerator() => ReadWithLockAction(() => ((IEnumerable)_list.ToArray()).GetEnumerator());
+
+
+ private void ReadWithLockAction(Action readAction)
+ {
+ if (!_lock.IsReadLockHeld)
+ {
+ _lock.EnterReadLock();
+ try
+ {
+ readAction();
+ }
+ finally
+ {
+ _lock.ExitReadLock();
+ }
+ }
+ else
+ {
+ readAction();
+ }
+ }
+
+ private TResult ReadWithLockAction(Func readAction)
+ {
+ if (!_lock.IsReadLockHeld)
+ {
+ _lock.EnterReadLock();
+ try
+ {
+ return readAction();
+ }
+ finally
+ {
+ _lock.ExitReadLock();
+ }
+ }
+
+ return readAction();
+ }
+
+ private void ReadAndWriteWithLockAction(Action writeAction, Action readAfterWriteAction)
+ {
+ lock (Synchronizer.LockObject)
+ {
+ _lock.EnterUpgradeableReadLock();
+ try
+ {
+ _lock.EnterWriteLock();
+ try
+ {
+ writeAction();
+ }
+ finally
+ {
+ _lock.ExitWriteLock();
+ }
+
+ _lock.EnterReadLock();
+
+ try
+ {
+ readAfterWriteAction();
+ }
+ finally
+ {
+ _lock.ExitReadLock();
+ }
+ }
+ finally
+ {
+ _lock.ExitUpgradeableReadLock();
+ }
+ }
+ }
+
+ private void ReadAndWriteWithLockAction(Func readBeforeWriteAction, Action writeAction, Action readAfterWriteAction)
+ {
+ lock (Synchronizer.LockObject)
+ {
+ _lock.EnterUpgradeableReadLock();
+ try
+ {
+ TResult readActionResult = readBeforeWriteAction();
+
+ _lock.EnterWriteLock();
+
+ try
+ {
+ writeAction(readActionResult);
+ }
+ finally
+ {
+ _lock.ExitWriteLock();
+ }
+
+ _lock.EnterReadLock();
+
+ try
+ {
+ readAfterWriteAction(readActionResult);
+ }
+ finally
+ {
+ _lock.ExitReadLock();
+ }
+ }
+ finally
+ {
+ _lock.ExitUpgradeableReadLock();
+ }
+ }
+
+ }
+
+ public event NotifyCollectionChangedEventHandler CollectionChanged;
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ public Synchronizer Synchronizer{get;}
+
+ public void Dispose() => Synchronizer.Dispose();
+
+ #region ICollection(non-generic) support
+ void ICollection.CopyTo(Array array, int index) => CopyTo(array.Cast().ToArray(), index);
+ bool ICollection.IsSynchronized { get; } = false;
+ object ICollection.SyncRoot { get; } = new object();
+ #endregion
+
+ #region IList(non-generic) support
+ object IList.this[int index]
+ {
+ get { return _list[index]; }
+ set { this[index] = (T)value; }
+ }
+ void IList.Remove(object value) => Remove((T)value);
+ bool IList.IsFixedSize { get; } = false;
+ int IList.Add(object value)
+ {
+ Add((T)value);
+ return _list.Count - 1;
+ }
+ bool IList.Contains(object value) => Contains((T)value);
+ int IList.IndexOf(object value) => IndexOf((T)value);
+ void IList.Insert(int index, object value) => Insert(index, (T)value);
+ #endregion
+ }
+
+ public static class ObservableSynchronizedCollectionExtensions
+ {
+ public static ObservableSynchronizedCollection ToSyncedObservableSynchronizedCollection(
+ this ISynchronizableNotifyChangedCollection source) => ToSyncedObservableSynchronizedCollection(source, _ => _);
+
+ public static ObservableSynchronizedCollection ToSyncedObservableSynchronizedCollection(
+ this ISynchronizableNotifyChangedCollection source,
+ Func converter)
+ {
+ lock (source.Synchronizer.LockObject)
+ {
+ var result = new ObservableSynchronizedCollection();
+ foreach (var item in source)
+ {
+ result.Add(converter(item));
+ }
+
+ var collectionChangedListener =
+ SynchronizableNotifyChangedCollectionHelper.CreateSynchronizableCollectionChangedEventListener(
+ source, result,
+ converter);
+ result.Synchronizer.EventListeners.Add(collectionChangedListener);
+ return result;
+ }
+ }
+ }
+}
diff --git a/LivetCask.Collections/ReadOnlyNotifyChangedCollection.cs b/LivetCask.StatefulModel/ReadOnlyNotifyChangedCollection.cs
similarity index 98%
rename from LivetCask.Collections/ReadOnlyNotifyChangedCollection.cs
rename to LivetCask.StatefulModel/ReadOnlyNotifyChangedCollection.cs
index ac057a1..4bce37e 100644
--- a/LivetCask.Collections/ReadOnlyNotifyChangedCollection.cs
+++ b/LivetCask.StatefulModel/ReadOnlyNotifyChangedCollection.cs
@@ -5,7 +5,7 @@
using System.Reflection;
using Livet.EventListeners;
-namespace Livet.Collections
+namespace Livet.StatefulModel
{
public sealed class ReadOnlyNotifyChangedCollection : ReadOnlyCollection, INotifyCollectionChanged, INotifyPropertyChanged, IDisposable
{
diff --git a/LivetCask.Collections/SortedObservableCollection.cs b/LivetCask.StatefulModel/SortedObservableCollection.cs
similarity index 99%
rename from LivetCask.Collections/SortedObservableCollection.cs
rename to LivetCask.StatefulModel/SortedObservableCollection.cs
index e8f1782..f913427 100644
--- a/LivetCask.Collections/SortedObservableCollection.cs
+++ b/LivetCask.StatefulModel/SortedObservableCollection.cs
@@ -3,7 +3,7 @@
using System.Linq;
using System.Reflection;
-namespace Livet.Collections
+namespace Livet.StatefulModel
{
public sealed class SortedObservableCollection : NotifyChangedCollection,ISynchronizableNotifyChangedCollection
{
diff --git a/LivetCask.Collections/SynchronizationContextCollection.cs b/LivetCask.StatefulModel/SynchronizationContextCollection.cs
similarity index 99%
rename from LivetCask.Collections/SynchronizationContextCollection.cs
rename to LivetCask.StatefulModel/SynchronizationContextCollection.cs
index bc5bae5..3e13df3 100644
--- a/LivetCask.Collections/SynchronizationContextCollection.cs
+++ b/LivetCask.StatefulModel/SynchronizationContextCollection.cs
@@ -3,7 +3,7 @@
using System.Linq;
using System.Threading;
-namespace Livet.Collections
+namespace Livet.StatefulModel
{
public sealed class SynchronizationContextCollection : NotifyChangedCollection,ISynchronizableNotifyChangedCollection
{
diff --git a/LivetCask.Collections/Synchronizer.cs b/LivetCask.StatefulModel/Synchronizer.cs
similarity index 97%
rename from LivetCask.Collections/Synchronizer.cs
rename to LivetCask.StatefulModel/Synchronizer.cs
index 4b6a736..9902475 100644
--- a/LivetCask.Collections/Synchronizer.cs
+++ b/LivetCask.StatefulModel/Synchronizer.cs
@@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Reflection;
-namespace Livet.Collections
+namespace Livet.StatefulModel
{
public class Synchronizer : IDisposable
{
diff --git a/LivetCask/LivetCask.csproj b/LivetCask/LivetCask.csproj
index a60ea47..aefb613 100644
--- a/LivetCask/LivetCask.csproj
+++ b/LivetCask/LivetCask.csproj
@@ -21,7 +21,7 @@ It supports .NET Framework 4.5.2 or later, .NET Core 3.0, and licensed as zlib/l
$(Version)
$(Version)
- $(Version)
+ $(PackageVersion)
true
Livet.snk
@@ -51,6 +51,7 @@ It supports .NET Framework 4.5.2 or later, .NET Core 3.0, and licensed as zlib/l
+
diff --git a/LivetCask/ObservableSynchronizedCollection.cs b/LivetCask/ObservableSynchronizedCollection.cs
deleted file mode 100644
index 20677c4..0000000
--- a/LivetCask/ObservableSynchronizedCollection.cs
+++ /dev/null
@@ -1,372 +0,0 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Collections.Specialized;
-using System.ComponentModel;
-using System.Linq;
-using System.Runtime.CompilerServices;
-using System.Threading;
-using Livet.Annotations;
-
-namespace Livet
-{
- ///
- /// スレッドセーフな変更通知コレクションです。
- ///
- /// コレクションアイテムの型
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")]
- [Serializable]
- public class ObservableSynchronizedCollection : IList, ICollection, INotifyCollectionChanged,
- INotifyPropertyChanged, IReadOnlyList
- {
- private const string ItemsString = "Item[]";
- [NotNull] private readonly IList _list;
-
- [NonSerialized] [NotNull] private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
-
- ///
- /// デフォルトコンストラクタ
- ///
- public ObservableSynchronizedCollection()
- {
- _list = new List();
- }
-
- ///
- /// コンストラクタ
- ///
- /// 初期値となるソース
- public ObservableSynchronizedCollection([NotNull] IEnumerable source)
- {
- if (source == null) throw new ArgumentNullException(nameof(source));
- _list = new List(source);
- }
-
- ///
- /// 全体を互換性のある1次元の配列にコピーします。コピー操作は、コピー先の配列の指定したインデックスから始まります。
- ///
- /// コピー先の配列
- /// コピー先の配列のどこからコピー操作をするかのインデックス
- public void CopyTo(Array array, int index)
- {
- CopyTo(array.Cast().ToArray(), index);
- }
-
- ///
- /// このコレクションがスレッドセーフであるかどうかを取得します。(常にtrueを返します)
- ///
- public bool IsSynchronized
- {
- get { return true; }
- }
-
- ///
- /// このコレクションへのスレッドセーフなアクセスに使用できる同期オブジェクトを返します。
- ///
- [field: NonSerialized]
- public object SyncRoot { get; } = new object();
-
- ///
- /// 指定したオブジェクトを検索し、最初に見つかった位置の 0 から始まるインデックスを返します。
- ///
- /// 検索するオブジェクト
- /// 最初に見つかった位置のインデックス
- public int IndexOf(T item)
- {
- return ReadWithLockAction(() => _list.IndexOf(item));
- }
-
- ///
- /// 指定したインデックスの位置に要素を挿入します。
- ///
- /// 指定するインデックス
- /// 挿入するオブジェクト
- public void Insert(int index, T item)
- {
- ReadAndWriteWithLockAction(() => _list.Insert(index, item),
- () =>
- {
- OnPropertyChanged(nameof(Count));
- OnPropertyChanged(ItemsString);
- OnCollectionChanged(
- new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
- });
- }
-
- ///
- /// 指定したインデックスにある要素を削除します。
- ///
- /// 指定するインデックス
- public void RemoveAt(int index)
- {
- ReadAndWriteWithLockAction(() => _list[index],
- removeItem => _list.RemoveAt(index),
- removeItem =>
- {
- OnPropertyChanged(nameof(Count));
- OnPropertyChanged(ItemsString);
- OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove,
- removeItem, index));
- });
- }
-
- public T this[int index]
- {
- get { return ReadWithLockAction(() => _list[index]); }
- set
- {
- ReadAndWriteWithLockAction(() => _list[index],
- oldItem => { _list[index] = value; },
- oldItem =>
- {
- OnPropertyChanged(ItemsString);
- OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace,
- _list[index], oldItem, index));
- });
- }
- }
-
- ///
- /// 末尾にオブジェクトを追加します。
- ///
- /// 追加するオブジェクト
- public void Add(T item)
- {
- ReadAndWriteWithLockAction(() => _list.Add(item),
- () =>
- {
- OnPropertyChanged(nameof(Count));
- OnPropertyChanged(ItemsString);
- OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item,
- _list.Count - 1));
- });
- }
-
- ///
- /// すべての要素を削除します。
- ///
- public void Clear()
- {
- ReadAndWriteWithLockAction(() => _list.Count,
- count =>
- {
- if (count == 0) return;
-
- _list.Clear();
- },
- count =>
- {
- if (count == 0) return;
-
- OnPropertyChanged(nameof(Count));
- OnPropertyChanged(ItemsString);
- OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
- });
- }
-
- ///
- /// ある要素がこのコレクションに含まれているかどうかを判断します。
- ///
- /// コレクションに含まれているか判断したい要素
- /// このコレクションに含まれているかどうか
- public bool Contains(T item)
- {
- return ReadWithLockAction(() => _list.Contains(item));
- }
-
- ///
- /// 全体を互換性のある1次元の配列にコピーします。コピー操作は、コピー先の配列の指定したインデックスから始まります。
- ///
- /// コピー先の配列
- /// コピー先の配列のどこからコピー操作をするかのインデックス
- public void CopyTo(T[] array, int arrayIndex)
- {
- ReadWithLockAction(() => _list.CopyTo(array, arrayIndex));
- }
-
- ///
- /// 実際に格納されている要素の数を取得します。
- ///
- public int Count
- {
- get { return ReadWithLockAction(() => _list.Count); }
- }
-
- ///
- /// このコレクションが読み取り専用かどうかを取得します。
- ///
- public bool IsReadOnly
- {
- get { return _list.IsReadOnly; }
- }
-
- ///
- /// 最初に見つかった特定のオブジェクトを削除します。
- ///
- /// 削除したいオブジェクト
- /// 削除できたかどうか
- public bool Remove(T item)
- {
- var result = false;
-
- ReadAndWriteWithLockAction(() => _list.IndexOf(item),
- index => { result = _list.Remove(item); },
- index =>
- {
- if (!result) return;
-
- OnPropertyChanged(nameof(Count));
- OnPropertyChanged(ItemsString);
- OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove,
- item, index));
- });
-
- return result;
- }
-
- ///
- /// 反復処理するためのスナップショットの列挙子を返します。
- ///
- /// 列挙子
- public IEnumerator GetEnumerator()
- {
- return ReadWithLockAction(() => ((IEnumerable) _list.ToArray()).GetEnumerator())
- ?? Enumerable.Empty().GetEnumerator();
- }
-
- ///
- /// 反復処理するためのスナップショットの列挙子を返します。
- ///
- /// 列挙子
- IEnumerator IEnumerable.GetEnumerator()
- {
- return GetEnumerator();
- }
-
- ///
- /// コレクションが変更された際に発生するイベントです。
- ///
- [field: NonSerialized]
- public event NotifyCollectionChangedEventHandler CollectionChanged;
-
- ///
- /// プロパティが変更された際に発生するイベントです。
- ///
- [field: NonSerialized]
- public event PropertyChangedEventHandler PropertyChanged;
-
- ///
- /// 指定されたインデックスの要素を指定されたインデックスに移動します。
- ///
- /// 移動したい要素のインデックス
- /// 移動先のインデックス
- public void Move(int oldIndex, int newIndex)
- {
- ReadAndWriteWithLockAction(() => _list[oldIndex],
- item =>
- {
- _list.RemoveAt(oldIndex);
- _list.Insert(newIndex, item);
- },
- item =>
- {
- OnPropertyChanged(ItemsString);
- OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, item,
- newIndex, oldIndex));
- });
- }
-
- ///
- /// CollectionChangedイベントを発生させます。
- ///
- /// NotifyCollectionChangedEventArgs
- protected void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
- {
- var threadSafeHandler = Interlocked.CompareExchange(ref CollectionChanged, null, null);
-
- threadSafeHandler?.Invoke(this, args);
- }
-
- ///
- /// PropertyChangedイベントを発生させます。
- ///
- /// 変更されたプロパティの名前
- [NotifyPropertyChangedInvocator]
- protected void OnPropertyChanged([CallerMemberName] [CanBeNull] string propertyName = "")
- {
- var threadSafeHandler = Interlocked.CompareExchange(ref PropertyChanged, null, null);
- threadSafeHandler?.Invoke(this, EventArgsFactory.GetPropertyChangedEventArgs(propertyName));
- }
-
- private void ReadWithLockAction([NotNull] Action readAction)
- {
- if (readAction == null) throw new ArgumentNullException(nameof(readAction));
-
- if (!_lock.IsReadLockHeld)
- {
- _lock.EnterReadLock();
- try { readAction(); }
- finally { _lock.ExitReadLock(); }
- }
- else
- readAction();
- }
-
- private TResult ReadWithLockAction([NotNull] Func readAction)
- {
- if (readAction == null) throw new ArgumentNullException(nameof(readAction));
-
- if (_lock.IsReadLockHeld) return readAction();
-
- _lock.EnterReadLock();
-
- try { return readAction(); }
- finally { _lock.ExitReadLock(); }
- }
-
- private void ReadAndWriteWithLockAction([NotNull] Action writeAction, [NotNull] Action readAfterWriteAction)
- {
- if (writeAction == null) throw new ArgumentNullException(nameof(writeAction));
- if (readAfterWriteAction == null) throw new ArgumentNullException(nameof(readAfterWriteAction));
-
- _lock.EnterUpgradeableReadLock();
- try
- {
- _lock.EnterWriteLock();
- try { writeAction(); }
- finally { _lock.ExitWriteLock(); }
-
- _lock.EnterReadLock();
-
- try { readAfterWriteAction(); }
- finally { _lock.ExitReadLock(); }
- }
- finally { _lock.ExitUpgradeableReadLock(); }
- }
-
- private void ReadAndWriteWithLockAction([NotNull] Func readBeforeWriteAction,
- [NotNull] Action writeAction, [NotNull] Action readAfterWriteAction)
- {
- if (readBeforeWriteAction == null) throw new ArgumentNullException(nameof(readBeforeWriteAction));
- if (writeAction == null) throw new ArgumentNullException(nameof(writeAction));
- if (readAfterWriteAction == null) throw new ArgumentNullException(nameof(readAfterWriteAction));
-
- _lock.EnterUpgradeableReadLock();
- try
- {
- var readActionResult = readBeforeWriteAction();
-
- _lock.EnterWriteLock();
-
- try { writeAction(readActionResult); }
- finally { _lock.ExitWriteLock(); }
-
- _lock.EnterReadLock();
-
- try { readAfterWriteAction(readActionResult); }
- finally { _lock.ExitReadLock(); }
- }
- finally { _lock.ExitUpgradeableReadLock(); }
- }
- }
-}
\ No newline at end of file
diff --git a/LivetCask/XmlDefinitions.cs b/LivetCask/XmlDefinitions.cs
deleted file mode 100644
index 992fe64..0000000
--- a/LivetCask/XmlDefinitions.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using System.Windows.Markup;
-
-[assembly: XmlnsDefinition("http://schemas.livet-mvvm.net/2011/wpf", "Livet")]
-[assembly: XmlnsDefinition("http://schemas.livet-mvvm.net/2011/wpf", "Livet.Commands")]
-[assembly: XmlnsDefinition("http://schemas.livet-mvvm.net/2011/wpf", "Livet.Converters")]
-[assembly: XmlnsDefinition("http://schemas.livet-mvvm.net/2011/wpf", "Livet.Messaging")]
-[assembly: XmlnsDefinition("http://schemas.livet-mvvm.net/2011/wpf", "Livet.Behaviors")]
-[assembly: XmlnsDefinition("http://schemas.livet-mvvm.net/2011/wpf", "Livet.Messaging.IO")]
-[assembly: XmlnsDefinition("http://schemas.livet-mvvm.net/2011/wpf", "Livet.Messaging.Windows")]
-[assembly: XmlnsDefinition("http://schemas.livet-mvvm.net/2011/wpf", "Livet.Behaviors.Messaging")]
-[assembly: XmlnsDefinition("http://schemas.livet-mvvm.net/2011/wpf", "Livet.Behaviors.Messaging.IO")]
-[assembly: XmlnsDefinition("http://schemas.livet-mvvm.net/2011/wpf", "Livet.Behaviors.Messaging.Windows")]
-[assembly: XmlnsDefinition("http://schemas.livet-mvvm.net/2011/wpf", "Livet.Behaviors.ControlBinding")]
-[assembly: XmlnsDefinition("http://schemas.livet-mvvm.net/2011/wpf", "Livet.Behaviors.ControlBinding.OneWay")]
\ No newline at end of file
diff --git a/NuGet/push.bat b/NuGet/push.bat
index 00f4310..81d9e76 100644
--- a/NuGet/push.bat
+++ b/NuGet/push.bat
@@ -1,6 +1,6 @@
@echo off
cd /d %~dp0
-set PACKAGEVERSION=3.2.0
+set PACKAGEVERSION=3.2.1-pre
dotnet build ..\Livet.Code.sln -c=Release
rem API Key nuget.org 擾 nuget setApiKey xxxx ŃZbgĂ
dotnet nuget push ..\LivetCask.Extensions\bin\Release\LivetExtensions.%PACKAGEVERSION%.nupkg -s https://www.nuget.org/api/v2/package
@@ -9,6 +9,8 @@ dotnet nuget push ..\LivetCask.Messaging\bin\Release\LivetCask.Messaging.%PACKAG
dotnet nuget push ..\LivetCask.EventListeners\bin\Release\LivetCask.EventListeners.%PACKAGEVERSION%.nupkg -s https://www.nuget.org/api/v2/package
dotnet nuget push ..\LivetCask.Converters\bin\Release\LivetCask.Converters.%PACKAGEVERSION%.nupkg -s https://www.nuget.org/api/v2/package
dotnet nuget push ..\LivetCask.Behaviors\bin\Release\LivetCask.Behaviors.%PACKAGEVERSION%.nupkg -s https://www.nuget.org/api/v2/package
+dotnet nuget push ..\LivetCask.Mvvm\bin\Release\LivetCask.Mvvm.%PACKAGEVERSION%.nupkg -s https://www.nuget.org/api/v2/package
+dotnet nuget push ..\LivetCask.StatefulModel\bin\Release\LivetCask.StatefulModel.%PACKAGEVERSION%.nupkg -s https://www.nuget.org/api/v2/package
dotnet nuget push ..\LivetCask\bin\Release\LivetCask.%PACKAGEVERSION%.nupkg -s https://www.nuget.org/api/v2/package
mkdir dist
@@ -18,5 +20,7 @@ copy ..\LivetCask.Messaging\bin\Release\LivetCask.Messaging.%PACKAGEVERSION%.nup
copy ..\LivetCask.EventListeners\bin\Release\LivetCask.EventListeners.%PACKAGEVERSION%.nupkg .\dist
copy ..\LivetCask.Converters\bin\Release\LivetCask.Converters.%PACKAGEVERSION%.nupkg .\dist
copy ..\LivetCask.Behaviors\bin\Release\LivetCask.Behaviors.%PACKAGEVERSION%.nupkg .\dist
+copy ..\LivetCask.Mvvm\bin\Release\LivetCask.Mvvm.%PACKAGEVERSION%.nupkg .\dist
+copy ..\LivetCask.StatefulModel\bin\Release\LivetCask.StatefulModel.%PACKAGEVERSION%.nupkg .\dist
copy ..\LivetCask\bin\Release\LivetCask.%PACKAGEVERSION%.nupkg .\dist
\ No newline at end of file