소스 검색

Merge branch 'linq-expression-expressionobserver' into selector-parse-no-sprache

Jeremy Koritzinsky 7 년 전
부모
커밋
de7e8d5567
100개의 변경된 파일1498개의 추가작업 그리고 906개의 파일을 삭제
  1. 152 4
      .editorconfig
  2. 6 2
      .gitignore
  3. 5 0
      .ncrunch/Avalonia.Designer.HostApp.NetFX.v3.ncrunchproject
  4. 5 0
      .ncrunch/BindingDemo.net461.v3.ncrunchproject
  5. 5 0
      .ncrunch/BindingDemo.netcoreapp2.0.v3.ncrunchproject
  6. 5 0
      .ncrunch/Previewer.v3.ncrunchproject
  7. 5 0
      .ncrunch/RemoteDemo.v3.ncrunchproject
  8. 5 0
      .ncrunch/RenderDemo.net461.v3.ncrunchproject
  9. 5 0
      .ncrunch/RenderDemo.netcoreapp2.0.v3.ncrunchproject
  10. 5 0
      .ncrunch/VirtualizationDemo.net461.v3.ncrunchproject
  11. 5 0
      .ncrunch/VirtualizationDemo.netcoreapp2.0.v3.ncrunchproject
  12. 50 4
      Avalonia.sln
  13. 191 178
      build.cake
  14. 2 2
      build/SkiaSharp.props
  15. 1 16
      build/XUnit.props
  16. 15 0
      cake.config
  17. 55 37
      packages.cake
  18. 5 9
      parameters.cake
  19. 0 0
      samples/BindingDemo/App.config
  20. 0 0
      samples/BindingDemo/App.xaml
  21. 1 1
      samples/BindingDemo/App.xaml.cs
  22. 0 0
      samples/BindingDemo/BindingDemo.csproj
  23. 2 2
      samples/BindingDemo/MainWindow.xaml
  24. 2 2
      samples/BindingDemo/MainWindow.xaml.cs
  25. 0 0
      samples/BindingDemo/TestItemView.xaml
  26. 1 1
      samples/BindingDemo/TestItemView.xaml.cs
  27. 1 1
      samples/BindingDemo/ViewModels/DataAnnotationsErrorViewModel.cs
  28. 1 1
      samples/BindingDemo/ViewModels/ExceptionErrorViewModel.cs
  29. 1 1
      samples/BindingDemo/ViewModels/IndeiErrorViewModel.cs
  30. 1 1
      samples/BindingDemo/ViewModels/MainWindowViewModel.cs
  31. 1 1
      samples/BindingDemo/ViewModels/NestedCommandViewModel.cs
  32. 1 1
      samples/BindingDemo/ViewModels/TestItem.cs
  33. 2 7
      samples/ControlCatalog.Android/Resources/Resource.Designer.cs
  34. 2 2
      samples/ControlCatalog.NetCore/Program.cs
  35. 2 2
      samples/ControlCatalog/SideBar.xaml
  36. 1 1
      samples/RemoteDemo/Program.cs
  37. 0 0
      samples/RemoteDemo/RemoteDemo.csproj
  38. 0 0
      samples/RenderDemo/App.config
  39. 1 1
      samples/RenderDemo/App.xaml
  40. 1 1
      samples/RenderDemo/App.xaml.cs
  41. 1 1
      samples/RenderDemo/MainWindow.xaml
  42. 2 2
      samples/RenderDemo/MainWindow.xaml.cs
  43. 0 0
      samples/RenderDemo/Pages/AnimationsPage.xaml
  44. 2 2
      samples/RenderDemo/Pages/AnimationsPage.xaml.cs
  45. 0 0
      samples/RenderDemo/Pages/ClippingPage.xaml
  46. 1 1
      samples/RenderDemo/Pages/ClippingPage.xaml.cs
  47. 0 0
      samples/RenderDemo/Pages/DrawingPage.xaml
  48. 1 1
      samples/RenderDemo/Pages/DrawingPage.xaml.cs
  49. 0 0
      samples/RenderDemo/RenderDemo.csproj
  50. 2 2
      samples/RenderDemo/SideBar.xaml
  51. 1 1
      samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs
  52. 1 1
      samples/RenderDemo/ViewModels/MainWindowViewModel.cs
  53. 0 0
      samples/VirtualizationDemo/App.config
  54. 0 0
      samples/VirtualizationDemo/App.xaml
  55. 1 1
      samples/VirtualizationDemo/App.xaml.cs
  56. 0 0
      samples/VirtualizationDemo/MainWindow.xaml
  57. 2 2
      samples/VirtualizationDemo/MainWindow.xaml.cs
  58. 1 1
      samples/VirtualizationDemo/Program.cs
  59. 1 1
      samples/VirtualizationDemo/ViewModels/ItemViewModel.cs
  60. 1 1
      samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs
  61. 0 0
      samples/VirtualizationDemo/VirtualizationDemo.csproj
  62. 0 1
      samples/interop/Direct3DInteropSample/MainWindow.cs
  63. 4 1
      src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs
  64. 0 2
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs
  65. 2 5
      src/Android/Avalonia.Android/Resources/Resource.Designer.cs
  66. 2 7
      src/Android/Avalonia.AndroidTestApplication/Resources/Resource.Designer.cs
  67. 1 1
      src/Avalonia.Animation/AnimatorKeyFrame.cs
  68. 70 122
      src/Avalonia.Base/AvaloniaObject.cs
  69. 16 65
      src/Avalonia.Base/AvaloniaObjectExtensions.cs
  70. 17 42
      src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs
  71. 20 5
      src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs
  72. 38 7
      src/Avalonia.Base/Data/Core/BindingExpression.cs
  73. 0 5
      src/Avalonia.Base/Data/Core/EmptyExpressionNode.cs
  74. 63 61
      src/Avalonia.Base/Data/Core/ExpressionNode.cs
  75. 91 83
      src/Avalonia.Base/Data/Core/ExpressionObserver.cs
  76. 0 15
      src/Avalonia.Base/Data/Core/ISettableNode.cs
  77. 27 17
      src/Avalonia.Base/Data/Core/IndexerExpressionNode.cs
  78. 13 8
      src/Avalonia.Base/Data/Core/IndexerNodeBase.cs
  79. 2 9
      src/Avalonia.Base/Data/Core/Parsers/ExpressionTreeParser.cs
  80. 20 17
      src/Avalonia.Base/Data/Core/Parsers/ExpressionVisitorNodeBuilder.cs
  81. 5 5
      src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs
  82. 5 5
      src/Avalonia.Base/Data/Core/Plugins/DataValidatiorBase.cs
  83. 2 3
      src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs
  84. 12 2
      src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessor.cs
  85. 9 10
      src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs
  86. 8 8
      src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
  87. 6 2
      src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs
  88. 38 30
      src/Avalonia.Base/Data/Core/Plugins/PropertyAccessorBase.cs
  89. 6 5
      src/Avalonia.Base/Data/Core/Plugins/PropertyError.cs
  90. 23 15
      src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs
  91. 38 0
      src/Avalonia.Base/Data/Core/SettableNode.cs
  92. 5 0
      src/Avalonia.Base/Data/Core/StreamBindingExtensions.cs
  93. 12 5
      src/Avalonia.Base/Data/Core/StreamNode.cs
  94. 5 4
      src/Avalonia.Base/IPriorityValueOwner.cs
  95. 2 2
      src/Avalonia.Base/PriorityValue.cs
  96. 0 42
      src/Avalonia.Base/Reactive/AvaloniaObservable.cs
  97. 46 0
      src/Avalonia.Base/Reactive/AvaloniaPropertyChangedObservable.cs
  98. 52 0
      src/Avalonia.Base/Reactive/AvaloniaPropertyObservable.cs
  99. 202 0
      src/Avalonia.Base/Reactive/LightweightObservableBase.cs
  100. 76 0
      src/Avalonia.Base/Reactive/SingleSubscriberObservableBase.cs

+ 152 - 4
.editorconfig

@@ -1,11 +1,159 @@
-; This file is for unifying the coding style for different editors and IDEs.
-; More information at http://EditorConfig.org
+# editorconfig.org
 
+# top-most EditorConfig file
 root = true
 
+# Default settings:
+# A newline ending every file
+# Use 4 spaces as indentation
 [*]
-end_of_line = CRLF
+insert_final_newline = true
+indent_style = space
+indent_size = 4
 
+# C# files
 [*.cs]
-indent_style = space
+# New line preferences
+csharp_new_line_before_open_brace = all
+csharp_new_line_before_else = true
+csharp_new_line_before_catch = true
+csharp_new_line_before_finally = true
+csharp_new_line_before_members_in_object_initializers = true
+csharp_new_line_before_members_in_anonymous_types = true
+csharp_new_line_between_query_expression_clauses = true
+
+# Indentation preferences
+csharp_indent_block_contents = true
+csharp_indent_braces = false
+csharp_indent_case_contents = true
+csharp_indent_switch_labels = true
+csharp_indent_labels = one_less_than_current
+
+# avoid this. unless absolutely necessary
+dotnet_style_qualification_for_field = false:suggestion
+dotnet_style_qualification_for_property = false:suggestion
+dotnet_style_qualification_for_method = false:suggestion
+dotnet_style_qualification_for_event = false:suggestion
+
+# prefer var
+csharp_style_var_for_built_in_types = true
+csharp_style_var_when_type_is_apparent = true
+csharp_style_var_elsewhere = true:suggestion
+
+# use language keywords instead of BCL types
+dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
+dotnet_style_predefined_type_for_member_access = true:suggestion
+
+# name all constant fields using PascalCase
+dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols  = constant_fields
+dotnet_naming_rule.constant_fields_should_be_pascal_case.style    = pascal_case_style
+
+dotnet_naming_symbols.constant_fields.applicable_kinds   = field
+dotnet_naming_symbols.constant_fields.required_modifiers = const
+
+dotnet_naming_style.pascal_case_style.capitalization = pascal_case
+
+# static fields should have s_ prefix
+dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion
+dotnet_naming_rule.static_fields_should_have_prefix.symbols  = static_fields
+dotnet_naming_rule.static_fields_should_have_prefix.style    = static_prefix_style
+
+dotnet_naming_symbols.static_fields.applicable_kinds   = field
+dotnet_naming_symbols.static_fields.required_modifiers = static
+
+dotnet_naming_style.static_prefix_style.required_prefix = s_
+dotnet_naming_style.static_prefix_style.capitalization = camel_case 
+
+# internal and private fields should be _camelCase
+dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion
+dotnet_naming_rule.camel_case_for_private_internal_fields.symbols  = private_internal_fields
+dotnet_naming_rule.camel_case_for_private_internal_fields.style    = camel_case_underscore_style
+
+dotnet_naming_symbols.private_internal_fields.applicable_kinds = field
+dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal
+
+dotnet_naming_style.camel_case_underscore_style.required_prefix = _
+dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case 
+
+# use accessibility modifiers
+dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
+
+# Code style defaults
+dotnet_sort_system_directives_first = true
+csharp_preserve_single_line_blocks = true
+csharp_preserve_single_line_statements = false
+
+# Expression-level preferences
+dotnet_style_object_initializer = true:suggestion
+dotnet_style_collection_initializer = true:suggestion
+dotnet_style_explicit_tuple_names = true:suggestion
+dotnet_style_coalesce_expression = true:suggestion
+dotnet_style_null_propagation = true:suggestion
+
+# Expression-bodied members
+csharp_style_expression_bodied_methods = false:none
+csharp_style_expression_bodied_constructors = false:none
+csharp_style_expression_bodied_operators = false:none
+csharp_style_expression_bodied_properties = true:none
+csharp_style_expression_bodied_indexers = true:none
+csharp_style_expression_bodied_accessors = true:none
+
+# Pattern matching
+csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
+csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
+csharp_style_inlined_variable_declaration = true:suggestion
+
+# Null checking preferences
+csharp_style_throw_expression = true:suggestion
+csharp_style_conditional_delegate_call = true:suggestion
+
+# Space preferences
+csharp_space_after_cast = false
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_after_comma = true
+csharp_space_after_dot = false
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_after_semicolon_in_for_statement = true
+csharp_space_around_binary_operators = before_and_after
+csharp_space_around_declaration_statements = do_not_ignore
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_before_comma = false
+csharp_space_before_dot = false
+csharp_space_before_open_square_brackets = false
+csharp_space_before_semicolon_in_for_statement = false
+csharp_space_between_empty_square_brackets = false
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_declaration_name_and_open_parenthesis = false
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+csharp_space_between_parentheses = false
+csharp_space_between_square_brackets = false
+
+# Xaml files
+[*.xaml]
 indent_size = 4
+
+# Xml project files
+[*.{csproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}]
+indent_size = 2
+
+# Xml build files
+[*.builds]
+indent_size = 2
+
+# Xml files
+[*.{xml,stylecop,resx,ruleset}]
+indent_size = 2
+
+# Xml config files
+[*.{props,targets,config,nuspec}]
+indent_size = 2
+
+# Shell scripts
+[*.sh]
+end_of_line = lf
+[*.{cmd, bat}]
+end_of_line = crlf

+ 6 - 2
.gitignore

@@ -176,5 +176,9 @@ nuget
 Avalonia.XBuild.sln
 project.lock.json
 .idea/*
-**/obj-Skia/*
-**/obj-Direct2D1/*
+
+
+##################
+## BenchmarkDotNet
+##################
+BenchmarkDotNet.Artifacts/

+ 5 - 0
.ncrunch/Avalonia.Designer.HostApp.NetFX.v3.ncrunchproject

@@ -0,0 +1,5 @@
+<ProjectConfiguration>
+  <Settings>
+    <IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
+  </Settings>
+</ProjectConfiguration>

+ 5 - 0
.ncrunch/BindingDemo.net461.v3.ncrunchproject

@@ -0,0 +1,5 @@
+<ProjectConfiguration>
+  <Settings>
+    <IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
+  </Settings>
+</ProjectConfiguration>

+ 5 - 0
.ncrunch/BindingDemo.netcoreapp2.0.v3.ncrunchproject

@@ -0,0 +1,5 @@
+<ProjectConfiguration>
+  <Settings>
+    <IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
+  </Settings>
+</ProjectConfiguration>

+ 5 - 0
.ncrunch/Previewer.v3.ncrunchproject

@@ -0,0 +1,5 @@
+<ProjectConfiguration>
+  <Settings>
+    <IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
+  </Settings>
+</ProjectConfiguration>

+ 5 - 0
.ncrunch/RemoteDemo.v3.ncrunchproject

@@ -0,0 +1,5 @@
+<ProjectConfiguration>
+  <Settings>
+    <IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
+  </Settings>
+</ProjectConfiguration>

+ 5 - 0
.ncrunch/RenderDemo.net461.v3.ncrunchproject

@@ -0,0 +1,5 @@
+<ProjectConfiguration>
+  <Settings>
+    <IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
+  </Settings>
+</ProjectConfiguration>

+ 5 - 0
.ncrunch/RenderDemo.netcoreapp2.0.v3.ncrunchproject

@@ -0,0 +1,5 @@
+<ProjectConfiguration>
+  <Settings>
+    <IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
+  </Settings>
+</ProjectConfiguration>

+ 5 - 0
.ncrunch/VirtualizationDemo.net461.v3.ncrunchproject

@@ -0,0 +1,5 @@
+<ProjectConfiguration>
+  <Settings>
+    <IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
+  </Settings>
+</ProjectConfiguration>

+ 5 - 0
.ncrunch/VirtualizationDemo.netcoreapp2.0.v3.ncrunchproject

@@ -0,0 +1,5 @@
+<ProjectConfiguration>
+  <Settings>
+    <IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
+  </Settings>
+</ProjectConfiguration>

+ 50 - 4
Avalonia.sln

@@ -58,6 +58,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{9B9E
 EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{A689DEF5-D50F-4975-8B72-124C9EB54066}"
 	ProjectSection(SolutionItems) = preProject
+		.editorconfig = .editorconfig
 		src\Shared\SharedAssemblyInfo.cs = src\Shared\SharedAssemblyInfo.cs
 	EndProjectSection
 EndProject
@@ -71,7 +72,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Markup", "src\Mark
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Markup.UnitTests", "tests\Avalonia.Markup.UnitTests\Avalonia.Markup.UnitTests.csproj", "{8EF392D5-1416-45AA-9956-7CBBC3229E8A}"
 EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BindingTest", "samples\BindingTest\BindingTest.csproj", "{08B3E6B9-1CD5-443C-9F61-6D49D1C5F162}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BindingDemo", "samples\BindingDemo\BindingDemo.csproj", "{08B3E6B9-1CD5-443C-9F61-6D49D1C5F162}"
 EndProject
 Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "RenderHelpers", "src\Shared\RenderHelpers\RenderHelpers.shproj", "{3C4C0CB4-0C0F-4450-A37B-148C84FF905F}"
 EndProject
@@ -109,7 +110,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.DesignerSupport.Te
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.DesignerSupport.TestApp", "tests\Avalonia.DesignerSupport.TestApp\Avalonia.DesignerSupport.TestApp.csproj", "{F1381F98-4D24-409A-A6C5-1C5B1E08BB08}"
 EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VirtualizationTest", "samples\VirtualizationTest\VirtualizationTest.csproj", "{FBCAF3D0-2808-4934-8E96-3F607594517B}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VirtualizationDemo", "samples\VirtualizationDemo\VirtualizationDemo.csproj", "{FBCAF3D0-2808-4934-8E96-3F607594517B}"
 EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Interop", "Interop", "{A0CC0258-D18C-4AB3-854F-7101680FC3F9}"
 EndProject
@@ -117,7 +118,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsInteropTest", "sampl
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.DotNetFrameworkRuntime", "src\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj", "{4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0}"
 EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RenderTest", "samples\RenderTest\RenderTest.csproj", "{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RenderDemo", "samples\RenderDemo\RenderDemo.csproj", "{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}"
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.Android", "samples\ControlCatalog.Android\ControlCatalog.Android.csproj", "{29132311-1848-4FD6-AE0C-4FF841151BD3}"
 EndProject
@@ -170,7 +171,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Skia.RenderTests",
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Remote.Protocol", "src\Avalonia.Remote.Protocol\Avalonia.Remote.Protocol.csproj", "{D78A720C-C0C6-478B-8564-F167F9BDD01B}"
 EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RemoteTest", "samples\RemoteTest\RemoteTest.csproj", "{E2999E4A-9086-401F-898C-AEB0AD38E676}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RemoteDemo", "samples\RemoteDemo\RemoteDemo.csproj", "{E2999E4A-9086-401F-898C-AEB0AD38E676}"
 EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{4ED8B739-6F4E-4CD4-B993-545E6B5CE637}"
 EndProject
@@ -184,6 +185,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.MonoMac", "src\OSX
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Designer.HostApp.NetFX", "src\tools\Avalonia.Designer.HostApp.NetFX\Avalonia.Designer.HostApp.NetFX.csproj", "{4ADA61C8-D191-428D-9066-EF4F0D86520F}"
 EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Skia.UnitTests", "tests\Avalonia.Skia.UnitTests\Avalonia.Skia.UnitTests.csproj", "{E1240B49-7B4B-4371-A00E-068778C5CF0B}"
+EndProject
 Global
 	GlobalSection(SharedMSBuildProjectFiles) = preSolution
 		src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
@@ -396,6 +399,7 @@ Global
 		{3E908F67-5543-4879-A1DC-08EACE79B3CD}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
 		{3E908F67-5543-4879-A1DC-08EACE79B3CD}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
 		{3E908F67-5543-4879-A1DC-08EACE79B3CD}.Debug|NetCoreOnly.ActiveCfg = Debug|Any CPU
+		{3E908F67-5543-4879-A1DC-08EACE79B3CD}.Debug|NetCoreOnly.Build.0 = Debug|Any CPU
 		{3E908F67-5543-4879-A1DC-08EACE79B3CD}.Debug|x86.ActiveCfg = Debug|Any CPU
 		{3E908F67-5543-4879-A1DC-08EACE79B3CD}.Debug|x86.Build.0 = Debug|Any CPU
 		{3E908F67-5543-4879-A1DC-08EACE79B3CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -405,6 +409,7 @@ Global
 		{3E908F67-5543-4879-A1DC-08EACE79B3CD}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
 		{3E908F67-5543-4879-A1DC-08EACE79B3CD}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
 		{3E908F67-5543-4879-A1DC-08EACE79B3CD}.Release|NetCoreOnly.ActiveCfg = Release|Any CPU
+		{3E908F67-5543-4879-A1DC-08EACE79B3CD}.Release|NetCoreOnly.Build.0 = Release|Any CPU
 		{3E908F67-5543-4879-A1DC-08EACE79B3CD}.Release|x86.ActiveCfg = Release|Any CPU
 		{3E908F67-5543-4879-A1DC-08EACE79B3CD}.Release|x86.Build.0 = Release|Any CPU
 		{62024B2D-53EB-4638-B26B-85EEAA54866E}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
@@ -2469,6 +2474,46 @@ Global
 		{4ADA61C8-D191-428D-9066-EF4F0D86520F}.Release|NetCoreOnly.ActiveCfg = Release|Any CPU
 		{4ADA61C8-D191-428D-9066-EF4F0D86520F}.Release|x86.ActiveCfg = Release|Any CPU
 		{4ADA61C8-D191-428D-9066-EF4F0D86520F}.Release|x86.Build.0 = Release|Any CPU
+		{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
+		{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
+		{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
+		{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
+		{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Ad-Hoc|NetCoreOnly.ActiveCfg = Debug|Any CPU
+		{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Ad-Hoc|NetCoreOnly.Build.0 = Debug|Any CPU
+		{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
+		{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
+		{E1240B49-7B4B-4371-A00E-068778C5CF0B}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
+		{E1240B49-7B4B-4371-A00E-068778C5CF0B}.AppStore|Any CPU.Build.0 = Debug|Any CPU
+		{E1240B49-7B4B-4371-A00E-068778C5CF0B}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
+		{E1240B49-7B4B-4371-A00E-068778C5CF0B}.AppStore|iPhone.Build.0 = Debug|Any CPU
+		{E1240B49-7B4B-4371-A00E-068778C5CF0B}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{E1240B49-7B4B-4371-A00E-068778C5CF0B}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{E1240B49-7B4B-4371-A00E-068778C5CF0B}.AppStore|NetCoreOnly.ActiveCfg = Debug|Any CPU
+		{E1240B49-7B4B-4371-A00E-068778C5CF0B}.AppStore|NetCoreOnly.Build.0 = Debug|Any CPU
+		{E1240B49-7B4B-4371-A00E-068778C5CF0B}.AppStore|x86.ActiveCfg = Debug|Any CPU
+		{E1240B49-7B4B-4371-A00E-068778C5CF0B}.AppStore|x86.Build.0 = Debug|Any CPU
+		{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+		{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Debug|iPhone.Build.0 = Debug|Any CPU
+		{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Debug|NetCoreOnly.ActiveCfg = Debug|Any CPU
+		{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Debug|NetCoreOnly.Build.0 = Debug|Any CPU
+		{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Debug|x86.Build.0 = Debug|Any CPU
+		{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Release|Any CPU.Build.0 = Release|Any CPU
+		{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Release|iPhone.ActiveCfg = Release|Any CPU
+		{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Release|iPhone.Build.0 = Release|Any CPU
+		{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+		{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+		{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Release|NetCoreOnly.ActiveCfg = Release|Any CPU
+		{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Release|NetCoreOnly.Build.0 = Release|Any CPU
+		{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Release|x86.ActiveCfg = Release|Any CPU
+		{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Release|x86.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -2521,6 +2566,7 @@ Global
 		{F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE} = {9B9E3891-2366-4253-A952-D08BCEB71098}
 		{CBFD5788-567D-401B-9DFA-74E4224025A0} = {A59C4C0A-64DF-4621-B450-2BA00D6F61E2}
 		{4ADA61C8-D191-428D-9066-EF4F0D86520F} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
+		{E1240B49-7B4B-4371-A00E-068778C5CF0B} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}

+ 191 - 178
build.cake

@@ -6,11 +6,13 @@
 #addin "nuget:?package=NuGet.Core&version=2.14.0"
 #tool "nuget:?package=NuGet.CommandLine&version=4.3.0"
 #tool "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2017.1.20170613.162720"
+
 ///////////////////////////////////////////////////////////////////////////////
 // TOOLS
 ///////////////////////////////////////////////////////////////////////////////
 
-#tool "nuget:?package=xunit.runner.console&version=2.3.0-beta5-build3769"
+#tool "nuget:?package=xunit.runner.console&version=2.3.1"
+#tool "nuget:?package=JetBrains.dotMemoryUnit&version=3.0.20171219.105559"
 
 ///////////////////////////////////////////////////////////////////////////////
 // USINGS
@@ -34,20 +36,31 @@ using NuGet;
 // PARAMETERS
 //////////////////////////////////////////////////////////////////////
 
-Parameters parameters = new Parameters(Context);
-Packages packages = new Packages(Context, parameters);
+class AvaloniaBuildData
+{
+    public AvaloniaBuildData(Parameters parameters, Packages packages)
+    {
+        Parameters = parameters;
+        Packages = packages;
+    }
+
+    public Parameters Parameters { get; }
+    public Packages Packages { get; }
+}
 
 ///////////////////////////////////////////////////////////////////////////////
 // SETUP
 ///////////////////////////////////////////////////////////////////////////////
 
-Setup(context =>
+Setup<AvaloniaBuildData>(context =>
 {
-    Information("Building version {0} of Avalonia ({1}, {2}, {3}) using version {4} of Cake.", 
+    var parameters = new Parameters(context);
+    var buildContext = new AvaloniaBuildData(parameters, new Packages(context, parameters));
+
+    Information("Building version {0} of Avalonia ({1}, {2}) using version {3} of Cake.", 
         parameters.Version,
         parameters.Platform,
         parameters.Configuration,
-        parameters.Target,
         typeof(ICakeContext).Assembly.GetName().Version.ToString());
 
     if (parameters.IsRunningOnAppVeyor)
@@ -55,8 +68,7 @@ Setup(context =>
         Information("Repository Name: " + BuildSystem.AppVeyor.Environment.Repository.Name);
         Information("Repository Branch: " + BuildSystem.AppVeyor.Environment.Repository.Branch);
     }
-
-    Information("Target: " + parameters.Target);
+    Information("Target:" + context.TargetTask.Name);
     Information("Platform: " + parameters.Platform);
     Information("Configuration: " + parameters.Configuration);
     Information("IsLocalBuild: " + parameters.IsLocalBuild);
@@ -70,13 +82,15 @@ Setup(context =>
     Information("IsReleasable: " + parameters.IsReleasable);
     Information("IsMyGetRelease: " + parameters.IsMyGetRelease);
     Information("IsNuGetRelease: " + parameters.IsNuGetRelease);
+
+    return buildContext;
 });
 
 ///////////////////////////////////////////////////////////////////////////////
 // TEARDOWN
 ///////////////////////////////////////////////////////////////////////////////
 
-Teardown(context =>
+Teardown<AvaloniaBuildData>((context, buildContext) =>
 {
     Information("Finished running tasks.");
 });
@@ -85,20 +99,20 @@ Teardown(context =>
 // TASKS
 ///////////////////////////////////////////////////////////////////////////////
 
-Task("Clean")
-    .Does(() =>
+Task("Clean-Impl")
+    .Does<AvaloniaBuildData>(data =>
 {
-    CleanDirectories(parameters.BuildDirs);
-    CleanDirectory(parameters.ArtifactsDir);
-    CleanDirectory(parameters.NugetRoot);
-    CleanDirectory(parameters.ZipRoot);
-    CleanDirectory(parameters.BinRoot);
+    CleanDirectories(data.Parameters.BuildDirs);
+    CleanDirectory(data.Parameters.ArtifactsDir);
+    CleanDirectory(data.Parameters.NugetRoot);
+    CleanDirectory(data.Parameters.ZipRoot);
+    CleanDirectory(data.Parameters.BinRoot);
 });
 
-Task("Restore-NuGet-Packages")
-    .IsDependentOn("Clean")
-    .WithCriteria(parameters.IsRunningOnWindows)
-    .Does(() =>
+Task("Restore-NuGet-Packages-Impl")
+    .WithCriteria<AvaloniaBuildData>((context, data) => data.Parameters.IsRunningOnWindows)
+    .WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.IsPlatformNetCoreOnly)
+    .Does<AvaloniaBuildData>(data =>
 {
     var maxRetryCount = 5;
     var toolTimeout = 2d;
@@ -115,13 +129,13 @@ Task("Restore-NuGet-Packages")
                 toolTimeout+=0.5;
             }})
         .Execute(()=> {
-                NuGetRestore(parameters.MSBuildSolution, new NuGetRestoreSettings {
+                NuGetRestore(data.Parameters.MSBuildSolution, new NuGetRestoreSettings {
                     ToolTimeout = TimeSpan.FromMinutes(toolTimeout)
                 });
         });
 });
 
-void DotNetCoreBuild()
+void DotNetCoreBuild(Parameters parameters)
 {
     var settings = new DotNetCoreBuildSettings 
     {
@@ -135,29 +149,28 @@ void DotNetCoreBuild()
     DotNetCoreBuild(parameters.MSBuildSolution, settings);
 }
 
-Task("Build")
-    .IsDependentOn("Restore-NuGet-Packages")
-    .Does(() =>
+Task("Build-Impl")
+    .Does<AvaloniaBuildData>(data =>
 {
-    if(parameters.IsRunningOnWindows)
+    if(data.Parameters.IsRunningOnWindows && !data.Parameters.IsPlatformNetCoreOnly)
     {
-        MSBuild(parameters.MSBuildSolution, settings => {
-            settings.SetConfiguration(parameters.Configuration);
+        MSBuild(data.Parameters.MSBuildSolution, settings => {
+            settings.SetConfiguration(data.Parameters.Configuration);
             settings.SetVerbosity(Verbosity.Minimal);
-            settings.WithProperty("Platform", "\"" + parameters.Platform + "\"");
+            settings.WithProperty("Platform", "\"" + data.Parameters.Platform + "\"");
             settings.WithProperty("UseRoslynPathHack", "true");
             settings.UseToolVersion(MSBuildToolVersion.VS2017);
             settings.WithProperty("Windows", "True");
             settings.SetNodeReuse(false);
+            settings.SetMaxCpuCount(0);
         });
     }
     else
     {
-        DotNetCoreBuild();
+        DotNetCoreBuild(data.Parameters);
     }
 });
 
-
 void RunCoreTest(string project, Parameters parameters, bool coreOnly = false)
 {
     if(!project.EndsWith(".csproj"))
@@ -180,83 +193,106 @@ void RunCoreTest(string project, Parameters parameters, bool coreOnly = false)
     }
 }
 
-Task("Run-Unit-Tests")
-    .IsDependentOn("Build")
-    .IsDependentOn("Run-Designer-Tests")
-    .IsDependentOn("Run-Render-Tests")
-    .WithCriteria(() => !parameters.SkipTests)
-    .Does(() => {
-        RunCoreTest("./tests/Avalonia.Base.UnitTests", parameters, false);
-        RunCoreTest("./tests/Avalonia.Controls.UnitTests", parameters, false);
-        RunCoreTest("./tests/Avalonia.Input.UnitTests", parameters, false);
-        RunCoreTest("./tests/Avalonia.Interactivity.UnitTests", parameters, false);
-        RunCoreTest("./tests/Avalonia.Layout.UnitTests", parameters, false);
-        RunCoreTest("./tests/Avalonia.Markup.UnitTests", parameters, false);
-        RunCoreTest("./tests/Avalonia.Markup.Xaml.UnitTests", parameters, false);
-        RunCoreTest("./tests/Avalonia.Styling.UnitTests", parameters, false);
-        RunCoreTest("./tests/Avalonia.Visuals.UnitTests", parameters, false);
-        if (parameters.IsRunningOnWindows)
-        {
-            RunCoreTest("./tests/Avalonia.Direct2D1.UnitTests", parameters, true);
-        }
-    });
+Task("Run-Unit-Tests-Impl")
+    .WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.SkipTests)
+    .Does<AvaloniaBuildData>(data =>
+{
+    RunCoreTest("./tests/Avalonia.Base.UnitTests", data.Parameters, false);
+    RunCoreTest("./tests/Avalonia.Controls.UnitTests", data.Parameters, false);
+    RunCoreTest("./tests/Avalonia.Input.UnitTests", data.Parameters, false);
+    RunCoreTest("./tests/Avalonia.Interactivity.UnitTests", data.Parameters, false);
+    RunCoreTest("./tests/Avalonia.Layout.UnitTests", data.Parameters, false);
+    RunCoreTest("./tests/Avalonia.Markup.UnitTests", data.Parameters, false);
+    RunCoreTest("./tests/Avalonia.Markup.Xaml.UnitTests", data.Parameters, false);
+    RunCoreTest("./tests/Avalonia.Styling.UnitTests", data.Parameters, false);
+    RunCoreTest("./tests/Avalonia.Visuals.UnitTests", data.Parameters, false);
+    RunCoreTest("./tests/Avalonia.Skia.UnitTests", data.Parameters, false);
+    if (data.Parameters.IsRunningOnWindows && !data.Parameters.IsPlatformNetCoreOnly)
+    {
+        RunCoreTest("./tests/Avalonia.Direct2D1.UnitTests", data.Parameters, true);
+    }
+});
 
-Task("Run-Designer-Tests")
-    .IsDependentOn("Build")
-    .WithCriteria(() => !parameters.SkipTests)
-    .Does(() => {
-        RunCoreTest("./tests/Avalonia.DesignerSupport.Tests", parameters, false);
-    });
+Task("Run-Designer-Tests-Impl")
+    .WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.SkipTests)
+    .Does<AvaloniaBuildData>(data =>
+{
+    RunCoreTest("./tests/Avalonia.DesignerSupport.Tests", data.Parameters, false);
+});
 
-Task("Run-Render-Tests")
-    .IsDependentOn("Build")
-    .WithCriteria(() => !parameters.SkipTests && parameters.IsRunningOnWindows)
-    .Does(() => {
-        RunCoreTest("./tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj", parameters, true);
-        RunCoreTest("./tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj", parameters, true);
-    });
+Task("Run-Render-Tests-Impl")
+    .WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.SkipTests)
+    .WithCriteria<AvaloniaBuildData>((context, data) => data.Parameters.IsRunningOnWindows)
+    .WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.IsPlatformNetCoreOnly)
+    .Does<AvaloniaBuildData>(data =>
+{
+    RunCoreTest("./tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj", data.Parameters, true);
+    RunCoreTest("./tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj", data.Parameters, true);
+});
 
-Task("Copy-Files")
-    .IsDependentOn("Run-Unit-Tests")
+Task("Run-Leak-Tests-Impl")
+    .WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.SkipTests)
+    .WithCriteria<AvaloniaBuildData>((context, data) => data.Parameters.IsRunningOnWindows)
+    .WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.IsPlatformNetCoreOnly)
     .Does(() =>
 {
-    CopyFiles(packages.BinFiles, parameters.BinRoot);
+    var dotMemoryUnit = Context.Tools.Resolve("dotMemoryUnit.exe");
+    var leakTestsExitCode = StartProcess(dotMemoryUnit, new ProcessSettings
+    {
+        Arguments = new ProcessArgumentBuilder()
+            .Append(Context.Tools.Resolve("xunit.console.x86.exe").FullPath)
+            .Append("--propagate-exit-code")
+            .Append("--")
+            .Append("tests\\Avalonia.LeakTests\\bin\\Release\\net461\\Avalonia.LeakTests.dll"),
+        Timeout = 120000
+    });
+
+    if (leakTestsExitCode != 0)
+    {
+        throw new Exception("Leak Tests failed");
+    }
 });
 
-Task("Zip-Files")
-    .IsDependentOn("Copy-Files")
-    .Does(() =>
+Task("Copy-Files-Impl")
+    .Does<AvaloniaBuildData>(data =>
 {
-    Zip(parameters.BinRoot, parameters.ZipCoreArtifacts);
-
-    Zip(parameters.ZipSourceControlCatalogDesktopDirs, 
-        parameters.ZipTargetControlCatalogDesktopDirs, 
-        GetFiles(parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.dll") + 
-        GetFiles(parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.config") + 
-        GetFiles(parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.so") + 
-        GetFiles(parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.dylib") + 
-        GetFiles(parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.exe"));
+    CopyFiles(data.Packages.BinFiles, data.Parameters.BinRoot);
 });
 
-Task("Create-NuGet-Packages")
-    .IsDependentOn("Run-Unit-Tests")
-    .IsDependentOn("Inspect")
-    .Does(() =>
+Task("Zip-Files-Impl")
+    .Does<AvaloniaBuildData>(data =>
+{
+    Zip(data.Parameters.BinRoot, data.Parameters.ZipCoreArtifacts);
+
+    Zip(data.Parameters.NugetRoot, data.Parameters.ZipNuGetArtifacts);
+
+    if (!data.Parameters.IsPlatformNetCoreOnly) {
+        Zip(data.Parameters.ZipSourceControlCatalogDesktopDirs, 
+            data.Parameters.ZipTargetControlCatalogDesktopDirs, 
+            GetFiles(data.Parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.dll") + 
+            GetFiles(data.Parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.config") + 
+            GetFiles(data.Parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.so") + 
+            GetFiles(data.Parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.dylib") + 
+            GetFiles(data.Parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.exe"));
+    }
+});
+
+Task("Create-NuGet-Packages-Impl")
+    .Does<AvaloniaBuildData>(data =>
 {
-    foreach(var nuspec in packages.NuspecNuGetSettings)
+    foreach(var nuspec in data.Packages.NuspecNuGetSettings)
     {
         NuGetPack(nuspec);
     }
 });
 
-Task("Publish-MyGet")
-    .IsDependentOn("Create-NuGet-Packages")
-    .WithCriteria(() => !parameters.IsLocalBuild)
-    .WithCriteria(() => !parameters.IsPullRequest)
-    .WithCriteria(() => parameters.IsMainRepo)
-    .WithCriteria(() => parameters.IsMasterBranch)
-    .WithCriteria(() => parameters.IsMyGetRelease)
-    .Does(() =>
+Task("Publish-MyGet-Impl")
+    .WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.IsLocalBuild)
+    .WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.IsPullRequest)
+    .WithCriteria<AvaloniaBuildData>((context, data) => data.Parameters.IsMainRepo)
+    .WithCriteria<AvaloniaBuildData>((context, data) => data.Parameters.IsMasterBranch)
+    .WithCriteria<AvaloniaBuildData>((context, data) => data.Parameters.IsMyGetRelease)
+    .Does<AvaloniaBuildData>(data =>
 {
     var apiKey = EnvironmentVariable("MYGET_API_KEY");
     if(string.IsNullOrEmpty(apiKey)) 
@@ -270,7 +306,7 @@ Task("Publish-MyGet")
         throw new InvalidOperationException("Could not resolve MyGet API url.");
     }
 
-    foreach(var nupkg in packages.NugetPackages)
+    foreach(var nupkg in data.Packages.NugetPackages)
     {
         NuGetPush(nupkg, new NuGetPushSettings {
             Source = apiUrl,
@@ -283,13 +319,12 @@ Task("Publish-MyGet")
     Information("Publish-MyGet Task failed, but continuing with next Task...");
 });
 
-Task("Publish-NuGet")
-    .IsDependentOn("Create-NuGet-Packages")
-    .WithCriteria(() => !parameters.IsLocalBuild)
-    .WithCriteria(() => !parameters.IsPullRequest)
-    .WithCriteria(() => parameters.IsMainRepo)
-    .WithCriteria(() => parameters.IsNuGetRelease)
-    .Does(() =>
+Task("Publish-NuGet-Impl")
+    .WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.IsLocalBuild)
+    .WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.IsPullRequest)
+    .WithCriteria<AvaloniaBuildData>((context, data) => data.Parameters.IsMainRepo)
+    .WithCriteria<AvaloniaBuildData>((context, data) => data.Parameters.IsNuGetRelease)
+    .Does<AvaloniaBuildData>(data =>
 {
     var apiKey = EnvironmentVariable("NUGET_API_KEY");
     if(string.IsNullOrEmpty(apiKey)) 
@@ -303,7 +338,7 @@ Task("Publish-NuGet")
         throw new InvalidOperationException("Could not resolve NuGet API url.");
     }
 
-    foreach(var nupkg in packages.NugetPackages)
+    foreach(var nupkg in data.Packages.NugetPackages)
     {
         NuGetPush(nupkg, new NuGetPushSettings {
             ApiKey = apiKey,
@@ -316,102 +351,80 @@ Task("Publish-NuGet")
     Information("Publish-NuGet Task failed, but continuing with next Task...");
 });
 
-Task("Run-Leak-Tests")
-    .WithCriteria(parameters.IsRunningOnWindows)
-    .IsDependentOn("Build")
+Task("Inspect-Impl")
+    .WithCriteria<AvaloniaBuildData>((context, data) => data.Parameters.IsRunningOnWindows)
+    .WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.IsPlatformNetCoreOnly)
     .Does(() =>
-    {
-        DotNetCoreRestore("tests\\Avalonia.LeakTests\\toolproject\\tool.csproj");
-        DotNetBuild("tests\\Avalonia.LeakTests\\toolproject\\tool.csproj", settings => settings.SetConfiguration("Release"));
-        var report = "tests\\Avalonia.LeakTests\\bin\\Release\\report.xml";
-        if(System.IO.File.Exists(report))
-            System.IO.File.Delete(report);
-
-        var toolXunitConsoleX86 = Context.Tools.Resolve("xunit.console.x86.exe").FullPath;
-        var proc = System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
+{
+    var badIssues = new []{"PossibleNullReferenceException"};
+    var whitelist = new []{"tests", "src\\android", "src\\ios",
+        "src\\markup\\avalonia.markup.xaml\\portablexaml\\portable.xaml.github"};
+    Information("Running code inspections");
+    
+    var exitCode = StartProcess(Context.Tools.Resolve("inspectcode.exe"),
+        new ProcessSettings
         {
-            FileName="tests\\Avalonia.LeakTests\\toolproject\\bin\\dotMemoryUnit.exe",
-            Arguments="-targetExecutable=\"" + toolXunitConsoleX86 + "\" -returnTargetExitCode  -- tests\\Avalonia.LeakTests\\bin\\Release\\Avalonia.LeakTests.dll -xml tests\\Avalonia.LeakTests\\bin\\Release\\report.xml ",
-            UseShellExecute = false,
+            Arguments = "--output=artifacts\\inspectcode.xml --profile=Avalonia.sln.DotSettings Avalonia.sln",
+            RedirectStandardOutput = true
         });
-        var st = System.Diagnostics.Stopwatch.StartNew();
-        while(!proc.HasExited && !System.IO.File.Exists(report))
-        {
-            if(st.Elapsed.TotalSeconds>60)
-            {
-                Error("Timed out, probably a bug in dotMemoryUnit");
-                proc.Kill();
-                throw new Exception("dotMemory issue");
-            }
-            proc.WaitForExit(100);
-        }
-        try{
-            proc.Kill();
-        }catch{}
-        var doc =  System.Xml.Linq.XDocument.Load(report);
-        if(doc.Root.Descendants("assembly").Any(x=>x.Attribute("failed").Value.ToString() != "0"))
-        {
-            throw new Exception("Tests failed");
-        }
-
-    });
 
-Task("Inspect")
-    .WithCriteria(parameters.IsRunningOnWindows)
-    .IsDependentOn("Restore-NuGet-Packages")
-    .Does(() =>
+    Information("Analyzing report");
+    var doc = XDocument.Parse(System.IO.File.ReadAllText("artifacts\\inspectcode.xml"));
+    var failBuild = false;
+    foreach(var xml in doc.Descendants("Issue"))
     {
-        var badIssues = new []{"PossibleNullReferenceException"};
-        var whitelist = new []{"tests", "src\\android", "src\\ios",
-            "src\\markup\\avalonia.markup.xaml\\portablexaml\\portable.xaml.github"};
-        Information("Running code inspections");
-        
-        StartProcess(Context.Tools.Resolve("inspectcode.exe"),
-            new ProcessSettings{ Arguments = "--output=artifacts\\inspectcode.xml --profile=Avalonia.sln.DotSettings Avalonia.sln" });
-        Information("Analyzing report");
-        var doc = XDocument.Parse(System.IO.File.ReadAllText("artifacts\\inspectcode.xml"));
-        var failBuild = false;
-        foreach(var xml in doc.Descendants("Issue"))
+        var typeId = xml.Attribute("TypeId").Value.ToString();
+        if(badIssues.Contains(typeId))
         {
-            var typeId = xml.Attribute("TypeId").Value.ToString();
-            if(badIssues.Contains(typeId))
-            {
-                var file = xml.Attribute("File").Value.ToString().ToLower();
-                if(whitelist.Any(wh => file.StartsWith(wh)))
-                    continue;
-                var line = xml.Attribute("Line").Value.ToString();
-                Error(typeId + " - " + file + " on line " + line);
-                failBuild = true;
-            }
+            var file = xml.Attribute("File").Value.ToString().ToLower();
+            if(whitelist.Any(wh => file.StartsWith(wh)))
+                continue;
+            var line = xml.Attribute("Line").Value.ToString();
+            Error(typeId + " - " + file + " on line " + line);
+            failBuild = true;
         }
-        if(failBuild)
-            throw new Exception("Issues found");
-    });
+    }
+    if(failBuild)
+        throw new Exception("Issues found");
+});
 
 ///////////////////////////////////////////////////////////////////////////////
 // TARGETS
 ///////////////////////////////////////////////////////////////////////////////
 
+Task("Run-Tests")
+    .IsDependentOn("Clean-Impl")
+    .IsDependentOn("Restore-NuGet-Packages-Impl")
+    .IsDependentOn("Build-Impl")
+    .IsDependentOn("Run-Unit-Tests-Impl")
+    .IsDependentOn("Run-Render-Tests-Impl")
+    .IsDependentOn("Run-Designer-Tests-Impl")
+    .IsDependentOn("Run-Leak-Tests-Impl");
+
 Task("Package")
-  .IsDependentOn("Create-NuGet-Packages");
+    .IsDependentOn("Run-Tests")
+    .IsDependentOn("Inspect-Impl")
+    .IsDependentOn("Create-NuGet-Packages-Impl");
 
-Task("Default").Does(() =>
-{
-    if(parameters.IsRunningOnWindows)
-        RunTarget("Package");
-    else
-        RunTarget("Run-Unit-Tests");
-});
 Task("AppVeyor")
-  .IsDependentOn("Zip-Files")
-  .IsDependentOn("Publish-MyGet")
-  .IsDependentOn("Publish-NuGet");
+  .IsDependentOn("Package")
+  .IsDependentOn("Copy-Files-Impl")
+  .IsDependentOn("Zip-Files-Impl")
+  .IsDependentOn("Publish-MyGet-Impl")
+  .IsDependentOn("Publish-NuGet-Impl");
 
 Task("Travis")
-  .IsDependentOn("Run-Unit-Tests");
+  .IsDependentOn("Run-Tests");
 
 ///////////////////////////////////////////////////////////////////////////////
 // EXECUTE
 ///////////////////////////////////////////////////////////////////////////////
 
-RunTarget(parameters.Target);
+var target = Context.Argument("target", "Default");
+
+if (target == "Default")
+{
+    target = Context.IsRunningOnWindows() ? "Package" : "Run-Tests";
+}
+
+RunTarget(target);

+ 2 - 2
build/SkiaSharp.props

@@ -1,6 +1,6 @@
 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup>
-    <PackageReference Include="SkiaSharp" Version="1.57.1" />
-    <PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="Avalonia.Skia.Linux.Natives" Version="1.57.1.3" />
+    <PackageReference Include="SkiaSharp" Version="1.60.0" />
+    <PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="Avalonia.Skia.Linux.Natives" Version="1.60.0.1" />
   </ItemGroup>
 </Project>

+ 1 - 16
build/XUnit.props

@@ -9,21 +9,6 @@
     <PackageReference Include="xunit.runner.console" Version="2.3.0" />
     <PackageReference Include="xunit.runner.visualstudio" Version="2.3.0" />
     <PackageReference Include="Xunit.SkippableFact" Version="1.3.6" />
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" />
   </ItemGroup>
-  <ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp2.0'">
-    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
-  </ItemGroup>
-  <PropertyGroup>
-    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
-  </PropertyGroup>
-  <Target Name="ForceGenerationOfBindingRedirects"
-          AfterTargets="ResolveAssemblyReferences"
-          BeforeTargets="GenerateBindingRedirects"
-          Condition="'$(AutoGenerateBindingRedirects)' == 'true'">
-    <PropertyGroup>
-      <!-- Needs to be set in a target because it has to be set after the initial evaluation in the common targets -->
-      <GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
-    </PropertyGroup>
-  </Target>
-  <Import Project="$(MSBuildThisFileDirectory)\NetFX.props" />
 </Project>

+ 15 - 0
cake.config

@@ -0,0 +1,15 @@
+; This is the default configuration file for Cake.
+; This file was downloaded from https://github.com/cake-build/resources
+
+[Nuget]
+Source=https://api.nuget.org/v3/index.json
+UseInProcessClient=true
+LoadDependencies=false
+
+[Paths]
+Tools=./tools
+Addins=./tools/Addins
+Modules=./tools/Modules
+
+[Settings]
+SkipVerification=false

+ 55 - 37
packages.cake

@@ -1,3 +1,6 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
 using System.Xml.Linq;
 
 public class Packages
@@ -9,12 +12,11 @@ public class Packages
     public string SkiaSharpVersion {get; private set; }
     public string SkiaSharpLinuxVersion {get; private set; }
     public Dictionary<string, IList<Tuple<string,string>>> PackageVersions{get; private set;}
-    
-       
-    
+
     class DependencyBuilder : List<NuSpecDependency>
     {
         Packages _parent;
+
         public DependencyBuilder(Packages parent)
         {
             _parent = parent;
@@ -24,8 +26,7 @@ public class Packages
         {
             return _parent.PackageVersions[name].First().Item1;
         }
-        
-        
+
         public DependencyBuilder Dep(string name, params string[] fws)
         {
             if(fws.Length == 0)
@@ -212,17 +213,33 @@ public class Packages
             };
         });
 
-        var toolsContent = new[] {
-            new NuSpecContent{
-                Source = ((FilePath)context.File("./src/tools/Avalonia.Designer.HostApp/bin/" + parameters.DirSuffix + "/netcoreapp2.0/Avalonia.Designer.HostApp.dll")).FullPath, 
-                Target = "tools/netcoreapp2.0/previewer"
-            },
-            new NuSpecContent{
-                Source = ((FilePath)context.File("./src/tools/Avalonia.Designer.HostApp.NetFx/bin/" + parameters.DirSuffix + "/Avalonia.Designer.HostApp.exe")).FullPath, 
-                Target = "tools/net461/previewer"
-            }
+        var toolHostApp = new NuSpecContent{
+            Source = ((FilePath)context.File("./src/tools/Avalonia.Designer.HostApp/bin/" + parameters.DirSuffix + "/netcoreapp2.0/Avalonia.Designer.HostApp.dll")).FullPath, 
+            Target = "tools/netcoreapp2.0/previewer"
+        };
+
+        var toolHostAppNetFx = new NuSpecContent{
+            Source = ((FilePath)context.File("./src/tools/Avalonia.Designer.HostApp.NetFx/bin/" + parameters.DirSuffix + "/Avalonia.Designer.HostApp.exe")).FullPath, 
+            Target = "tools/net461/previewer"
         };
 
+        IList<NuSpecContent> coreFiles;
+
+        if (!parameters.IsPlatformNetCoreOnly) {
+            var toolsContent = new[] { toolHostApp, toolHostAppNetFx };
+            coreFiles = coreLibrariesNuSpecContent
+                .Concat(win32CoreLibrariesNuSpecContent).Concat(net45RuntimePlatform)
+                .Concat(netcoreappCoreLibrariesNuSpecContent).Concat(netCoreRuntimePlatform)
+                .Concat(toolsContent)
+                .ToList();
+        } else {
+            var toolsContent = new[] { toolHostApp };
+            coreFiles = coreLibrariesNuSpecContent
+                .Concat(netcoreappCoreLibrariesNuSpecContent).Concat(netCoreRuntimePlatform)
+                .Concat(toolsContent)
+                .ToList();
+        }
+
         var nuspecNuGetSettingsCore = new []
         {
             ///////////////////////////////////////////////////////////////////////////////
@@ -253,13 +270,9 @@ public class Packages
                 }
                 .Deps(new string[]{null, "netcoreapp2.0"},
                     "System.ValueTuple", "System.ComponentModel.TypeConverter", "System.ComponentModel.Primitives",
-                    "System.Runtime.Serialization.Primitives", "System.Xml.XmlDocument", "System.Xml.ReaderWriter")
+                    "System.Runtime.Serialization.Primitives", "System.Xml.XmlDocument", "System.Xml.ReaderWriter", "System.Memory")
                 .ToArray(),
-                Files = coreLibrariesNuSpecContent
-                    .Concat(win32CoreLibrariesNuSpecContent).Concat(net45RuntimePlatform)
-                    .Concat(netcoreappCoreLibrariesNuSpecContent).Concat(netCoreRuntimePlatform)
-                    .Concat(toolsContent)
-                    .ToList(),
+                Files = coreFiles,
                 BasePath = context.Directory("./"),
                 OutputDirectory = parameters.NugetRoot
             },
@@ -451,22 +464,6 @@ public class Packages
                 BasePath = context.Directory("./"),
                 OutputDirectory = parameters.NugetRoot
             },
-            new NuGetPackSettings()
-            {
-                Id = "Avalonia.Win32.Interoperability",
-                Dependencies = new []
-                {
-                    new NuSpecDependency() { Id = "Avalonia.Win32", Version = parameters.Version },
-                    new NuSpecDependency() { Id = "Avalonia.Direct2D1", Version = parameters.Version },
-                    new NuSpecDependency() { Id = "SharpDX.Direct3D9", Version = SharpDXDirect3D9Version },
-                },
-                Files = new []
-                {
-                    new NuSpecContent { Source = "Avalonia.Win32.Interop/bin/" + parameters.DirSuffix + "/Avalonia.Win32.Interop.dll", Target = "lib/net45" }
-                },
-                BasePath = context.Directory("./src/Windows"),
-                OutputDirectory = parameters.NugetRoot
-            },
             ///////////////////////////////////////////////////////////////////////////////
             // Avalonia.LinuxFramebuffer
             ///////////////////////////////////////////////////////////////////////////////
@@ -487,11 +484,32 @@ public class Packages
             }
         };
 
+        var nuspecNuGetSettingInterop = new NuGetPackSettings()
+        {
+            Id = "Avalonia.Win32.Interoperability",
+            Dependencies = new []
+            {
+                new NuSpecDependency() { Id = "Avalonia.Win32", Version = parameters.Version },
+                new NuSpecDependency() { Id = "Avalonia.Direct2D1", Version = parameters.Version },
+                new NuSpecDependency() { Id = "SharpDX.Direct3D9", Version = SharpDXDirect3D9Version },
+            },
+            Files = new []
+            {
+                new NuSpecContent { Source = "Avalonia.Win32.Interop/bin/" + parameters.DirSuffix + "/Avalonia.Win32.Interop.dll", Target = "lib/net45" }
+            },
+            BasePath = context.Directory("./src/Windows"),
+            OutputDirectory = parameters.NugetRoot
+        };
+
         NuspecNuGetSettings = new List<NuGetPackSettings>();
 
         NuspecNuGetSettings.AddRange(nuspecNuGetSettingsCore);
         NuspecNuGetSettings.AddRange(nuspecNuGetSettingsDesktop);
-        NuspecNuGetSettings.AddRange(nuspecNuGetSettingsMobile);
+
+        if (!parameters.IsPlatformNetCoreOnly) {
+            NuspecNuGetSettings.Add(nuspecNuGetSettingInterop);
+            NuspecNuGetSettings.AddRange(nuspecNuGetSettingsMobile);
+        }
 
         NuspecNuGetSettings.ForEach((nuspec) => SetNuGetNuspecCommonProperties(nuspec));
 

+ 5 - 9
parameters.cake

@@ -1,6 +1,5 @@
 public class Parameters
 {
-    public string Target { get; private set; }
     public string Platform { get; private set; }
     public string Configuration { get; private set; }
     public bool SkipTests { get; private set; }
@@ -9,11 +8,11 @@ public class Parameters
     public string AssemblyInfoPath { get; private set; }
     public string ReleasePlatform { get; private set; }
     public string ReleaseConfiguration { get; private set; }
-    public string MSBuildSolution { get; private set; } 
-    public string XBuildSolution { get; private set; } 
+    public string MSBuildSolution { get; private set; }
     public bool IsPlatformAnyCPU { get; private set; }
     public bool IsPlatformX86 { get; private set; }
     public bool IsPlatformX64 { get; private set; }
+    public bool IsPlatformNetCoreOnly { get; private set; }
     public bool IsLocalBuild { get; private set; }
     public bool IsRunningOnUnix { get; private set; }
     public bool IsRunningOnWindows { get; private set; }
@@ -35,6 +34,7 @@ public class Parameters
     public DirectoryPathCollection BuildDirs { get; private set; }
     public string FileZipSuffix { get; private set; }
     public FilePath ZipCoreArtifacts { get; private set; }
+    public FilePath ZipNuGetArtifacts { get; private set; }
     public DirectoryPath ZipSourceControlCatalogDesktopDirs { get; private set; }
     public FilePath ZipTargetControlCatalogDesktopDirs { get; private set; }
 
@@ -43,7 +43,6 @@ public class Parameters
         var buildSystem = context.BuildSystem();
 
         // ARGUMENTS
-        Target = context.Argument("target", "Default");
         Platform = context.Argument("platform", "Any CPU");
         Configuration = context.Argument("configuration", "Release");
         SkipTests = context.HasArgument("skip-tests");
@@ -55,12 +54,12 @@ public class Parameters
         ReleasePlatform = "Any CPU";
         ReleaseConfiguration = "Release";
         MSBuildSolution = "./Avalonia.sln";
-        XBuildSolution = "./Avalonia.XBuild.sln";
 
         // PARAMETERS
         IsPlatformAnyCPU = StringComparer.OrdinalIgnoreCase.Equals(Platform, "Any CPU");
         IsPlatformX86 = StringComparer.OrdinalIgnoreCase.Equals(Platform, "x86");
         IsPlatformX64 = StringComparer.OrdinalIgnoreCase.Equals(Platform, "x64");
+        IsPlatformNetCoreOnly = StringComparer.OrdinalIgnoreCase.Equals(Platform, "NetCoreOnly");
         IsLocalBuild = buildSystem.IsLocalBuild;
         IsRunningOnUnix = context.IsRunningOnUnix();
         IsRunningOnWindows = context.IsRunningOnWindows();
@@ -73,7 +72,6 @@ public class Parameters
         IsReleasable = StringComparer.OrdinalIgnoreCase.Equals(ReleasePlatform, Platform) 
                     && StringComparer.OrdinalIgnoreCase.Equals(ReleaseConfiguration, Configuration);
         IsMyGetRelease = !IsTagged && IsReleasable;
-        
 
         // VERSION
         Version = context.Argument("force-nuget-version", context.ParseAssemblyInfo(AssemblyInfoPath).AssemblyVersion);
@@ -105,14 +103,12 @@ public class Parameters
         NugetRoot = ArtifactsDir.Combine("nuget");
         ZipRoot = ArtifactsDir.Combine("zip");
         BinRoot = ArtifactsDir.Combine("bin");
-
         BuildDirs = context.GetDirectories("**/bin") + context.GetDirectories("**/obj");
-
         DirSuffix = Configuration;
         DirSuffixIOS = "iPhone" + "/" + Configuration;
-
         FileZipSuffix = Version + ".zip";
         ZipCoreArtifacts = ZipRoot.CombineWithFilePath("Avalonia-" + FileZipSuffix);
+        ZipNuGetArtifacts = ZipRoot.CombineWithFilePath("Avalonia-NuGet-" + FileZipSuffix);
         ZipSourceControlCatalogDesktopDirs = (DirectoryPath)context.Directory("./samples/ControlCatalog.Desktop/bin/" + DirSuffix + "/net461");
         ZipTargetControlCatalogDesktopDirs = ZipRoot.CombineWithFilePath("ControlCatalog.Desktop-" + FileZipSuffix);
     }

+ 0 - 0
samples/BindingTest/App.config → samples/BindingDemo/App.config


+ 0 - 0
samples/BindingTest/App.xaml → samples/BindingDemo/App.xaml


+ 1 - 1
samples/BindingTest/App.xaml.cs → samples/BindingDemo/App.xaml.cs

@@ -5,7 +5,7 @@ using Avalonia.Logging.Serilog;
 using Avalonia.Markup.Xaml;
 using Serilog;
 
-namespace BindingTest
+namespace BindingDemo
 {
     public class App : Application
     {

+ 0 - 0
samples/BindingTest/BindingTest.csproj → samples/BindingDemo/BindingDemo.csproj


+ 2 - 2
samples/BindingTest/MainWindow.xaml → samples/BindingDemo/MainWindow.xaml

@@ -1,7 +1,7 @@
 <Window xmlns="https://github.com/avaloniaui"
         xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
-        xmlns:vm="clr-namespace:BindingTest.ViewModels"
-        xmlns:local="clr-namespace:BindingTest"
+        xmlns:vm="clr-namespace:BindingDemo.ViewModels"
+        xmlns:local="clr-namespace:BindingDemo"
         Title="AvaloniaUI Bindings Test"
         Width="800"
         Height="600">

+ 2 - 2
samples/BindingTest/MainWindow.xaml.cs → samples/BindingDemo/MainWindow.xaml.cs

@@ -1,9 +1,9 @@
-using BindingTest.ViewModels;
+using BindingDemo.ViewModels;
 using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Markup.Xaml;
 
-namespace BindingTest
+namespace BindingDemo
 {
     public class MainWindow : Window
     {

+ 0 - 0
samples/BindingTest/TestItemView.xaml → samples/BindingDemo/TestItemView.xaml


+ 1 - 1
samples/BindingTest/TestItemView.xaml.cs → samples/BindingDemo/TestItemView.xaml.cs

@@ -1,7 +1,7 @@
 using Avalonia.Controls;
 using Avalonia.Markup.Xaml;
 
-namespace BindingTest
+namespace BindingDemo
 {
     public class TestItemView : UserControl
     {

+ 1 - 1
samples/BindingTest/ViewModels/DataAnnotationsErrorViewModel.cs → samples/BindingDemo/ViewModels/DataAnnotationsErrorViewModel.cs

@@ -3,7 +3,7 @@
 
 using System.ComponentModel.DataAnnotations;
 
-namespace BindingTest.ViewModels
+namespace BindingDemo.ViewModels
 {
     public class DataAnnotationsErrorViewModel
     {

+ 1 - 1
samples/BindingTest/ViewModels/ExceptionErrorViewModel.cs → samples/BindingDemo/ViewModels/ExceptionErrorViewModel.cs

@@ -4,7 +4,7 @@
 using ReactiveUI;
 using System;
 
-namespace BindingTest.ViewModels
+namespace BindingDemo.ViewModels
 {
     public class ExceptionErrorViewModel : ReactiveObject
     {

+ 1 - 1
samples/BindingTest/ViewModels/IndeiErrorViewModel.cs → samples/BindingDemo/ViewModels/IndeiErrorViewModel.cs

@@ -6,7 +6,7 @@ using System;
 using System.ComponentModel;
 using System.Collections;
 
-namespace BindingTest.ViewModels
+namespace BindingDemo.ViewModels
 {
     public class IndeiErrorViewModel : ReactiveObject, INotifyDataErrorInfo
     {

+ 1 - 1
samples/BindingTest/ViewModels/MainWindowViewModel.cs → samples/BindingDemo/ViewModels/MainWindowViewModel.cs

@@ -6,7 +6,7 @@ using System.Reactive.Linq;
 using System.Threading.Tasks;
 using System.Threading;
 
-namespace BindingTest.ViewModels
+namespace BindingDemo.ViewModels
 {
     public class MainWindowViewModel : ReactiveObject
     {

+ 1 - 1
samples/BindingTest/ViewModels/NestedCommandViewModel.cs → samples/BindingDemo/ViewModels/NestedCommandViewModel.cs

@@ -6,7 +6,7 @@ using System.Text;
 using System.Threading.Tasks;
 using System.Windows.Input;
 
-namespace BindingTest.ViewModels
+namespace BindingDemo.ViewModels
 {
     public class NestedCommandViewModel : ReactiveObject
     {

+ 1 - 1
samples/BindingTest/ViewModels/TestItem.cs → samples/BindingDemo/ViewModels/TestItem.cs

@@ -1,6 +1,6 @@
 using ReactiveUI;
 
-namespace BindingTest.ViewModels
+namespace BindingDemo.ViewModels
 {
     public class TestItem : ReactiveObject
     {

+ 2 - 7
samples/ControlCatalog.Android/Resources/Resource.Designer.cs

@@ -28,8 +28,6 @@ namespace ControlCatalog.Android
 		{
 			global::Avalonia.Android.Resource.String.ApplicationName = global::ControlCatalog.Android.Resource.String.ApplicationName;
 			global::Avalonia.Android.Resource.String.Hello = global::ControlCatalog.Android.Resource.String.Hello;
-			global::Avalonia.Android.Resource.String.library_name = global::ControlCatalog.Android.Resource.String.library_name;
-			global::Splat.Resource.String.library_name = global::ControlCatalog.Android.Resource.String.library_name;
 		}
 		
 		public partial class Attribute
@@ -96,14 +94,11 @@ namespace ControlCatalog.Android
 		public partial class String
 		{
 			
-			// aapt resource value: 0x7f040002
-			public const int ApplicationName = 2130968578;
-			
 			// aapt resource value: 0x7f040001
-			public const int Hello = 2130968577;
+			public const int ApplicationName = 2130968577;
 			
 			// aapt resource value: 0x7f040000
-			public const int library_name = 2130968576;
+			public const int Hello = 2130968576;
 			
 			static String()
 			{

+ 2 - 2
samples/ControlCatalog.NetCore/Program.cs

@@ -1,9 +1,9 @@
 using System;
 using System.Diagnostics;
 using System.Linq;
-using System.Runtime.InteropServices;
 using System.Threading;
 using Avalonia;
+using Avalonia.Skia;
 
 namespace ControlCatalog.NetCore
 {
@@ -37,7 +37,7 @@ namespace ControlCatalog.NetCore
         /// This method is needed for IDE previewer infrastructure
         /// </summary>
         public static AppBuilder BuildAvaloniaApp()
-            => AppBuilder.Configure<App>().UsePlatformDetect().UseReactiveUI();
+            => AppBuilder.Configure<App>().UsePlatformDetect().UseSkia().UseReactiveUI();
 
         static void ConsoleSilencer()
         {

+ 2 - 2
samples/ControlCatalog/SideBar.xaml

@@ -8,7 +8,7 @@
             <TabStrip Name="PART_TabStrip"
                       MemberSelector="{x:Static TabControl.HeaderSelector}"
                       Items="{TemplateBinding Items}"
-                      SelectedIndex="{TemplateBinding Path=SelectedIndex, Mode=TwoWay}">
+                      SelectedIndex="{TemplateBinding SelectedIndex, Mode=TwoWay}">
               <TabStrip.ItemsPanel>
                 <ItemsPanelTemplate>
                   <StackPanel Orientation="Vertical"/>
@@ -20,7 +20,7 @@
                     Margin="8 0 0 0"
                     MemberSelector="{x:Static TabControl.ContentSelector}"
                     Items="{TemplateBinding Items}"
-                    SelectedIndex="{TemplateBinding Path=SelectedIndex}"
+                    SelectedIndex="{TemplateBinding SelectedIndex}"
                     PageTransition="{TemplateBinding PageTransition}"
                     Grid.Row="1"/>
         </DockPanel>

+ 1 - 1
samples/RemoteTest/Program.cs → samples/RemoteDemo/Program.cs

@@ -9,7 +9,7 @@ using Avalonia.Remote.Protocol;
 using Avalonia.Threading;
 using ControlCatalog;
 
-namespace RemoteTest
+namespace RemoteDemo
 {
     class Program
     {

+ 0 - 0
samples/RemoteTest/RemoteTest.csproj → samples/RemoteDemo/RemoteDemo.csproj


+ 0 - 0
samples/RenderTest/App.config → samples/RenderDemo/App.config


+ 1 - 1
samples/RenderTest/App.xaml → samples/RenderDemo/App.xaml

@@ -2,6 +2,6 @@
   <Application.Styles>
     <StyleInclude Source="resm:Avalonia.Themes.Default.DefaultTheme.xaml?assembly=Avalonia.Themes.Default"/>
     <StyleInclude Source="resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default"/>
-    <StyleInclude Source="resm:RenderTest.SideBar.xaml"/>
+    <StyleInclude Source="resm:RenderDemo.SideBar.xaml"/>
   </Application.Styles>
 </Application>

+ 1 - 1
samples/RenderTest/App.xaml.cs → samples/RenderDemo/App.xaml.cs

@@ -5,7 +5,7 @@ using Avalonia;
 using Avalonia.Logging.Serilog;
 using Avalonia.Markup.Xaml;
 
-namespace RenderTest
+namespace RenderDemo
 {
     public class App : Application
     {

+ 1 - 1
samples/RenderTest/MainWindow.xaml → samples/RenderDemo/MainWindow.xaml

@@ -1,6 +1,6 @@
 <Window xmlns="https://github.com/avaloniaui"
         Title="AvaloniaUI Rendering Test"
-        xmlns:pages="clr-namespace:RenderTest.Pages"
+        xmlns:pages="clr-namespace:RenderDemo.Pages"
         Width="800"
         Height="600">
   <DockPanel>

+ 2 - 2
samples/RenderTest/MainWindow.xaml.cs → samples/RenderDemo/MainWindow.xaml.cs

@@ -5,10 +5,10 @@ using System;
 using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Markup.Xaml;
-using RenderTest.ViewModels;
+using RenderDemo.ViewModels;
 using ReactiveUI;
 
-namespace RenderTest
+namespace RenderDemo
 {
     public class MainWindow : Window
     {

+ 0 - 0
samples/RenderTest/Pages/AnimationsPage.xaml → samples/RenderDemo/Pages/AnimationsPage.xaml


+ 2 - 2
samples/RenderTest/Pages/AnimationsPage.xaml.cs → samples/RenderDemo/Pages/AnimationsPage.xaml.cs

@@ -7,9 +7,9 @@ using Avalonia.Data;
 using Avalonia.Input;
 using Avalonia.Markup.Xaml;
 using Avalonia.Media;
-using RenderTest.ViewModels;
+using RenderDemo.ViewModels;
 
-namespace RenderTest.Pages
+namespace RenderDemo.Pages
 {
     public class AnimationsPage : UserControl
     {

+ 0 - 0
samples/RenderTest/Pages/ClippingPage.xaml → samples/RenderDemo/Pages/ClippingPage.xaml


+ 1 - 1
samples/RenderTest/Pages/ClippingPage.xaml.cs → samples/RenderDemo/Pages/ClippingPage.xaml.cs

@@ -7,7 +7,7 @@ using Avalonia.Data;
 using Avalonia.Markup.Xaml;
 using Avalonia.Media;
 
-namespace RenderTest.Pages
+namespace RenderDemo.Pages
 {
     public class ClippingPage : UserControl
     {

+ 0 - 0
samples/RenderTest/Pages/DrawingPage.xaml → samples/RenderDemo/Pages/DrawingPage.xaml


+ 1 - 1
samples/RenderTest/Pages/DrawingPage.xaml.cs → samples/RenderDemo/Pages/DrawingPage.xaml.cs

@@ -1,7 +1,7 @@
 using Avalonia.Controls;
 using Avalonia.Markup.Xaml;
 
-namespace RenderTest.Pages
+namespace RenderDemo.Pages
 {
     public class DrawingPage : UserControl
     {

+ 0 - 0
samples/RenderTest/RenderTest.csproj → samples/RenderDemo/RenderDemo.csproj


+ 2 - 2
samples/RenderTest/SideBar.xaml → samples/RenderDemo/SideBar.xaml

@@ -9,7 +9,7 @@
             <TabStrip Name="PART_TabStrip"
                       MemberSelector="{x:Static TabControl.HeaderSelector}"
                       Items="{TemplateBinding Items}"
-                      SelectedIndex="{TemplateBinding Path=SelectedIndex, Mode=TwoWay}">
+                      SelectedIndex="{TemplateBinding SelectedIndex, Mode=TwoWay}">
               <TabStrip.ItemsPanel>
                 <ItemsPanelTemplate>
                   <StackPanel Orientation="Vertical"/>
@@ -21,7 +21,7 @@
                     Margin="8 0 0 0"
                     MemberSelector="{x:Static TabControl.ContentSelector}"
                     Items="{TemplateBinding Items}"
-                    SelectedIndex="{TemplateBinding Path=SelectedIndex}"
+                    SelectedIndex="{TemplateBinding SelectedIndex}"
                     PageTransition="{TemplateBinding PageTransition}"
                     Grid.Row="1"/>
         </DockPanel>

+ 1 - 1
samples/RenderTest/ViewModels/AnimationsPageViewModel.cs → samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs

@@ -2,7 +2,7 @@
 using ReactiveUI;
 using Avalonia.Animation;
 
-namespace RenderTest.ViewModels
+namespace RenderDemo.ViewModels
 {
     public class AnimationsPageViewModel : ReactiveObject
     {

+ 1 - 1
samples/RenderTest/ViewModels/MainWindowViewModel.cs → samples/RenderDemo/ViewModels/MainWindowViewModel.cs

@@ -1,7 +1,7 @@
 using System;
 using ReactiveUI;
 
-namespace RenderTest.ViewModels
+namespace RenderDemo.ViewModels
 {
     public class MainWindowViewModel : ReactiveObject
     {

+ 0 - 0
samples/VirtualizationTest/App.config → samples/VirtualizationDemo/App.config


+ 0 - 0
samples/VirtualizationTest/App.xaml → samples/VirtualizationDemo/App.xaml


+ 1 - 1
samples/VirtualizationTest/App.xaml.cs → samples/VirtualizationDemo/App.xaml.cs

@@ -4,7 +4,7 @@
 using Avalonia;
 using Avalonia.Markup.Xaml;
 
-namespace VirtualizationTest
+namespace VirtualizationDemo
 {
     public class App : Application
     {

+ 0 - 0
samples/VirtualizationTest/MainWindow.xaml → samples/VirtualizationDemo/MainWindow.xaml


+ 2 - 2
samples/VirtualizationTest/MainWindow.xaml.cs → samples/VirtualizationDemo/MainWindow.xaml.cs

@@ -4,9 +4,9 @@
 using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Markup.Xaml;
-using VirtualizationTest.ViewModels;
+using VirtualizationDemo.ViewModels;
 
-namespace VirtualizationTest
+namespace VirtualizationDemo
 {
     public class MainWindow : Window
     {

+ 1 - 1
samples/VirtualizationTest/Program.cs → samples/VirtualizationDemo/Program.cs

@@ -7,7 +7,7 @@ using Avalonia.Controls;
 using Avalonia.Logging.Serilog;
 using Serilog;
 
-namespace VirtualizationTest
+namespace VirtualizationDemo
 {
     class Program
     {

+ 1 - 1
samples/VirtualizationTest/ViewModels/ItemViewModel.cs → samples/VirtualizationDemo/ViewModels/ItemViewModel.cs

@@ -4,7 +4,7 @@
 using System;
 using ReactiveUI;
 
-namespace VirtualizationTest.ViewModels
+namespace VirtualizationDemo.ViewModels
 {
     internal class ItemViewModel : ReactiveObject
     {

+ 1 - 1
samples/VirtualizationTest/ViewModels/MainWindowViewModel.cs → samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs

@@ -9,7 +9,7 @@ using Avalonia.Controls;
 using Avalonia.Controls.Primitives;
 using ReactiveUI;
 
-namespace VirtualizationTest.ViewModels
+namespace VirtualizationDemo.ViewModels
 {
     internal class MainWindowViewModel : ReactiveObject
     {

+ 0 - 0
samples/VirtualizationTest/VirtualizationTest.csproj → samples/VirtualizationDemo/VirtualizationDemo.csproj


+ 0 - 1
samples/interop/Direct3DInteropSample/MainWindow.cs

@@ -88,7 +88,6 @@ namespace Direct3DInteropSample
             context.ClearDepthStencilView(depthView, DepthStencilClearFlags.Depth, 1.0f, 0);
             context.ClearRenderTargetView(renderView, Color.White);
 
-            var time = 50;
             // Update WorldViewProj Matrix
             var worldViewProj = Matrix.RotationX((float) _model.RotationX) * Matrix.RotationY((float) _model.RotationY) *
                                 Matrix.RotationZ((float) _model.RotationZ)

+ 4 - 1
src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs

@@ -110,7 +110,10 @@ namespace Avalonia.Android.Platform.SkiaPlatform
         {
             //Not supported
         }
-        
 
+        public void SetTopmost(bool value)
+        {
+            //Not supported
+        }
     }
 }

+ 0 - 2
src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs

@@ -126,8 +126,6 @@ namespace Avalonia.Android.Platform.Specific.Helpers
             return e.Action != MotionEventActions.Up;
         }
 
-        private Paint _paint;
-
         public void Dispose()
         {
             HandleEvents = false;

+ 2 - 5
src/Android/Avalonia.Android/Resources/Resource.Designer.cs

@@ -40,14 +40,11 @@ namespace Avalonia.Android
 		public partial class String
 		{
 			
-			// aapt resource value: 0x7f020002
-			public static int ApplicationName = 2130837506;
-			
 			// aapt resource value: 0x7f020001
-			public static int Hello = 2130837505;
+			public static int ApplicationName = 2130837505;
 			
 			// aapt resource value: 0x7f020000
-			public static int library_name = 2130837504;
+			public static int Hello = 2130837504;
 			
 			static String()
 			{

+ 2 - 7
src/Android/Avalonia.AndroidTestApplication/Resources/Resource.Designer.cs

@@ -28,8 +28,6 @@ namespace Avalonia.AndroidTestApplication
 		{
 			global::Avalonia.Android.Resource.String.ApplicationName = global::Avalonia.AndroidTestApplication.Resource.String.ApplicationName;
 			global::Avalonia.Android.Resource.String.Hello = global::Avalonia.AndroidTestApplication.Resource.String.Hello;
-			global::Avalonia.Android.Resource.String.library_name = global::Avalonia.AndroidTestApplication.Resource.String.library_name;
-			global::Splat.Resource.String.library_name = global::Avalonia.AndroidTestApplication.Resource.String.library_name;
 		}
 		
 		public partial class Attribute
@@ -64,14 +62,11 @@ namespace Avalonia.AndroidTestApplication
 		public partial class String
 		{
 			
-			// aapt resource value: 0x7f030002
-			public const int ApplicationName = 2130903042;
-			
 			// aapt resource value: 0x7f030001
-			public const int Hello = 2130903041;
+			public const int ApplicationName = 2130903041;
 			
 			// aapt resource value: 0x7f030000
-			public const int library_name = 2130903040;
+			public const int Hello = 2130903040;
 			
 			static String()
 			{

+ 1 - 1
src/Avalonia.Animation/AnimatorKeyFrame.cs

@@ -9,7 +9,7 @@ namespace Avalonia.Animation
 {
     /// <summary>
     /// Defines a KeyFrame that is used for
-    /// <see cref="Animators"/> objects.
+    /// <see cref="Animator{T}"/> objects.
     /// </summary>
     public class AnimatorKeyFrame
     {

+ 70 - 122
src/Avalonia.Base/AvaloniaObject.cs

@@ -10,6 +10,7 @@ using System.Reactive.Linq;
 using Avalonia.Data;
 using Avalonia.Diagnostics;
 using Avalonia.Logging;
+using Avalonia.Reactive;
 using Avalonia.Threading;
 using Avalonia.Utilities;
 
@@ -28,17 +29,11 @@ namespace Avalonia
         /// </summary>
         private IAvaloniaObject _inheritanceParent;
 
-        /// <summary>
-        /// The set values/bindings on this object.
-        /// </summary>
-        private readonly Dictionary<AvaloniaProperty, PriorityValue> _values =
-            new Dictionary<AvaloniaProperty, PriorityValue>();
-
         /// <summary>
         /// Maintains a list of direct property binding subscriptions so that the binding source
         /// doesn't get collected.
         /// </summary>
-        private List<IDisposable> _directBindings;
+        private List<DirectBindingSubscription> _directBindings;
 
         /// <summary>
         /// Event handler for <see cref="INotifyPropertyChanged"/> implementation.
@@ -51,6 +46,7 @@ namespace Avalonia
         private EventHandler<AvaloniaPropertyChangedEventArgs> _propertyChanged;
 
         private DeferredSetter<AvaloniaProperty, object> _directDeferredSetter;
+        private ValueStore _values;
 
         /// <summary>
         /// Delayed setter helper for direct properties. Used to fix #855.
@@ -227,9 +223,20 @@ namespace Avalonia
             {
                 return ((IDirectPropertyAccessor)GetRegistered(property)).GetValue(this);
             }
+            else if (_values != null)
+            {
+                var result = _values.GetValue(property);
+
+                if (result == AvaloniaProperty.UnsetValue)
+                {
+                    result = GetDefaultValue(property);
+                }
+
+                return result;
+            }
             else
             {
-                return GetValueInternal(property);
+                return GetDefaultValue(property);
             }
         }
 
@@ -256,7 +263,7 @@ namespace Avalonia
             Contract.Requires<ArgumentNullException>(property != null);
             VerifyAccess();
 
-            return _values.TryGetValue(property, out PriorityValue value) ? value.IsAnimating : false;
+            return _values?.IsAnimating(property) ?? false;
         }
 
         /// <summary>
@@ -273,14 +280,7 @@ namespace Avalonia
             Contract.Requires<ArgumentNullException>(property != null);
             VerifyAccess();
 
-            PriorityValue value;
-
-            if (_values.TryGetValue(property, out value))
-            {
-                return value.Value != AvaloniaProperty.UnsetValue;
-            }
-
-            return false;
+            return _values?.IsSet(property) ?? false;
         }
 
         /// <summary>
@@ -359,36 +359,15 @@ namespace Avalonia
                     property, 
                     description);
 
-                IDisposable subscription = null;
-
                 if (_directBindings == null)
                 {
-                    _directBindings = new List<IDisposable>();
+                    _directBindings = new List<DirectBindingSubscription>();
                 }
 
-                subscription = source
-                    .Select(x => CastOrDefault(x, property.PropertyType))
-                    .Do(_ => { }, () => _directBindings.Remove(subscription))
-                    .Subscribe(x => SetDirectValue(property, x));
-
-                _directBindings.Add(subscription);
-
-                return Disposable.Create(() =>
-                {
-                    subscription.Dispose();
-                    _directBindings.Remove(subscription);
-                });
+                return new DirectBindingSubscription(this, property, source);
             }
             else
             {
-                PriorityValue v;
-
-                if (!_values.TryGetValue(property, out v))
-                {
-                    v = CreatePriorityValue(property);
-                    _values.Add(property, v);
-                }
-
                 Logger.Verbose(
                     LogArea.Property,
                     this,
@@ -397,7 +376,12 @@ namespace Avalonia
                     description,
                     priority);
 
-                return v.Add(source, (int)priority);
+                if (_values == null)
+                {
+                    _values = new ValueStore(this);
+                }
+
+                return _values.AddBinding(property, source, priority);
             }
         }
 
@@ -428,20 +412,12 @@ namespace Avalonia
         public void Revalidate(AvaloniaProperty property)
         {
             VerifyAccess();
-            PriorityValue value;
-
-            if (_values.TryGetValue(property, out value))
-            {
-                value.Revalidate();
-            }
+            _values?.Revalidate(property);
         }
 
         /// <inheritdoc/>
-        void IPriorityValueOwner.Changed(PriorityValue sender, object oldValue, object newValue)
+        void IPriorityValueOwner.Changed(AvaloniaProperty property, int priority, object oldValue, object newValue)
         {
-            var property = sender.Property;
-            var priority = (BindingPriority)sender.ValuePriority;
-
             oldValue = (oldValue == AvaloniaProperty.UnsetValue) ?
                 GetDefaultValue(property) :
                 oldValue;
@@ -451,7 +427,7 @@ namespace Avalonia
 
             if (!Equals(oldValue, newValue))
             {
-                RaisePropertyChanged(property, oldValue, newValue, priority);
+                RaisePropertyChanged(property, oldValue, newValue, (BindingPriority)priority);
 
                 Logger.Verbose(
                     LogArea.Property,
@@ -460,14 +436,14 @@ namespace Avalonia
                     property,
                     oldValue,
                     newValue,
-                    priority);
+                    (BindingPriority)priority);
             }
         }
 
         /// <inheritdoc/>
-        void IPriorityValueOwner.BindingNotificationReceived(PriorityValue sender, BindingNotification notification)
+        void IPriorityValueOwner.BindingNotificationReceived(AvaloniaProperty property, BindingNotification notification)
         {
-            UpdateDataValidation(sender.Property, notification);
+            UpdateDataValidation(property, notification);
         }
 
         /// <inheritdoc/>
@@ -480,10 +456,7 @@ namespace Avalonia
         /// Gets all priority values set on the object.
         /// </summary>
         /// <returns>A collection of property/value tuples.</returns>
-        internal IDictionary<AvaloniaProperty, PriorityValue> GetSetValues()
-        {
-            return _values;
-        }
+        internal IDictionary<AvaloniaProperty, PriorityValue> GetSetValues() => _values?.GetSetValues();
 
         /// <summary>
         /// Forces revalidation of properties when a property value changes.
@@ -672,68 +645,18 @@ namespace Avalonia
             }
         }
 
-        /// <summary>
-        /// Creates a <see cref="PriorityValue"/> for a <see cref="AvaloniaProperty"/>.
-        /// </summary>
-        /// <param name="property">The property.</param>
-        /// <returns>The <see cref="PriorityValue"/>.</returns>
-        private PriorityValue CreatePriorityValue(AvaloniaProperty property)
-        {
-            var validate = ((IStyledPropertyAccessor)property).GetValidationFunc(GetType());
-            Func<object, object> validate2 = null;
-
-            if (validate != null)
-            {
-                validate2 = v => validate(this, v);
-            }
-
-            PriorityValue result = new PriorityValue(
-                this,
-                property,
-                property.PropertyType, 
-                validate2);
-
-            return result;
-        }
-
         /// <summary>
         /// Gets the default value for a property.
         /// </summary>
         /// <param name="property">The property.</param>
         /// <returns>The default value.</returns>
-        private object GetDefaultValue(AvaloniaProperty property)
+        internal object GetDefaultValue(AvaloniaProperty property)
         {
             if (property.Inherits && InheritanceParent is AvaloniaObject aobj)
-                return aobj.GetValueInternal(property);
+                return aobj.GetValue(property);
             return ((IStyledPropertyAccessor) property).GetDefaultValue(GetType());
         }
 
-        /// <summary>
-        /// Gets a <see cref="AvaloniaProperty"/> value
-        /// without check for registered as this can slow getting the value
-        /// this method is intended for internal usage in AvaloniaObject only
-        /// it's called only after check the property is registered
-        /// </summary>
-        /// <param name="property">The property.</param>
-        /// <returns>The value.</returns>
-        private object GetValueInternal(AvaloniaProperty property)
-        {
-            object result = AvaloniaProperty.UnsetValue;
-            PriorityValue value;
-
-            if (_values.TryGetValue(property, out value))
-            {
-                result = value.Value;
-            }
-
-            if (result == AvaloniaProperty.UnsetValue)
-            {
-                result = GetDefaultValue(property);
-            }
-
-            return result;
-        }
-
         /// <summary>
         /// Sets the value of a direct property.
         /// </summary>
@@ -814,21 +737,13 @@ namespace Avalonia
                     originalValue?.GetType().FullName ?? "(null)"));
             }
 
-            PriorityValue v;
-
-            if (!_values.TryGetValue(property, out v))
+            if (_values == null)
             {
-                if (value == AvaloniaProperty.UnsetValue)
-                {
-                    return;
-                }
-
-                v = CreatePriorityValue(property);
-                _values.Add(property, v);
+                _values = new ValueStore(this);
             }
 
             LogPropertySet(property, value, priority);
-            v.SetValue(value, (int)priority);
+            _values.AddValue(property, value, (int)priority);
         }
 
         /// <summary>
@@ -908,5 +823,38 @@ namespace Avalonia
                 value,
                 priority);
         }
+
+        private class DirectBindingSubscription : IObserver<object>, IDisposable
+        {
+            readonly AvaloniaObject _owner;
+            readonly AvaloniaProperty _property;
+            IDisposable _subscription;
+
+            public DirectBindingSubscription(
+                AvaloniaObject owner,
+                AvaloniaProperty property,
+                IObservable<object> source)
+            {
+                _owner = owner;
+                _property = property;
+                _owner._directBindings.Add(this);
+                _subscription = source.Subscribe(this);
+            }
+
+            public void Dispose()
+            {
+                _subscription.Dispose();
+                _owner._directBindings.Remove(this);
+            }
+
+            public void OnCompleted() => Dispose();
+            public void OnError(Exception error) => Dispose();
+
+            public void OnNext(object value)
+            {
+                var castValue = CastOrDefault(value, _property.PropertyType);
+                _owner.SetDirectValue(_property, castValue);
+            }
+        }
     }
 }

+ 16 - 65
src/Avalonia.Base/AvaloniaObjectExtensions.cs

@@ -36,32 +36,15 @@ namespace Avalonia
         /// An observable which fires immediately with the current value of the property on the
         /// object and subsequently each time the property value changes.
         /// </returns>
+        /// <remarks>
+        /// The subscription to <paramref name="o"/> is created using a weak reference.
+        /// </remarks>
         public static IObservable<object> GetObservable(this IAvaloniaObject o, AvaloniaProperty property)
         {
             Contract.Requires<ArgumentNullException>(o != null);
             Contract.Requires<ArgumentNullException>(property != null);
 
-            return new AvaloniaObservable<object>(
-                observer =>
-                {
-                    EventHandler<AvaloniaPropertyChangedEventArgs> handler = (s, e) =>
-                    {
-                        if (e.Property == property)
-                        {
-                            observer.OnNext(e.NewValue);
-                        }
-                    };
-
-                    observer.OnNext(o.GetValue(property));
-
-                    o.PropertyChanged += handler;
-
-                    return Disposable.Create(() =>
-                    {
-                        o.PropertyChanged -= handler;
-                    });
-                },
-                GetDescription(o, property));
+            return new AvaloniaPropertyObservable<object>(o, property);
         }
 
         /// <summary>
@@ -74,51 +57,36 @@ namespace Avalonia
         /// An observable which fires immediately with the current value of the property on the
         /// object and subsequently each time the property value changes.
         /// </returns>
+        /// <remarks>
+        /// The subscription to <paramref name="o"/> is created using a weak reference.
+        /// </remarks>
         public static IObservable<T> GetObservable<T>(this IAvaloniaObject o, AvaloniaProperty<T> property)
         {
             Contract.Requires<ArgumentNullException>(o != null);
             Contract.Requires<ArgumentNullException>(property != null);
 
-            return o.GetObservable((AvaloniaProperty)property).Cast<T>();
+            return new AvaloniaPropertyObservable<T>(o, property);
         }
 
         /// <summary>
-        /// Gets an observable for a <see cref="AvaloniaProperty"/>.
+        /// Gets an observable that listens for property changed events for an
+        /// <see cref="AvaloniaProperty"/>.
         /// </summary>
         /// <param name="o">The object.</param>
-        /// <typeparam name="T">The type of the property.</typeparam>
         /// <param name="property">The property.</param>
         /// <returns>
-        /// An observable which when subscribed pushes the old and new values of the property each
-        /// time it is changed. Note that the observable returned from this method does not fire
-        /// with the current value of the property immediately.
+        /// An observable which when subscribed pushes the property changed event args
+        /// each time a <see cref="IAvaloniaObject.PropertyChanged"/> event is raised
+        /// for the specified property.
         /// </returns>
-        public static IObservable<Tuple<T, T>> GetObservableWithHistory<T>(
+        public static IObservable<AvaloniaPropertyChangedEventArgs> GetPropertyChangedObservable(
             this IAvaloniaObject o, 
-            AvaloniaProperty<T> property)
+            AvaloniaProperty property)
         {
             Contract.Requires<ArgumentNullException>(o != null);
             Contract.Requires<ArgumentNullException>(property != null);
 
-            return new AvaloniaObservable<Tuple<T, T>>(
-                observer =>
-                {
-                    EventHandler<AvaloniaPropertyChangedEventArgs> handler = (s, e) =>
-                    {
-                        if (e.Property == property)
-                        {
-                            observer.OnNext(Tuple.Create((T)e.OldValue, (T)e.NewValue));
-                        }
-                    };
-
-                    o.PropertyChanged += handler;
-
-                    return Disposable.Create(() =>
-                    {
-                        o.PropertyChanged -= handler;
-                    });
-                },
-                GetDescription(o, property));
+            return new AvaloniaPropertyChangedObservable(o, property);
         }
 
         /// <summary>
@@ -166,23 +134,6 @@ namespace Avalonia
                 o.GetObservable(property));
         }
 
-        /// <summary>
-        /// Gets a weak observable for a <see cref="AvaloniaProperty"/>.
-        /// </summary>
-        /// <param name="o">The object.</param>
-        /// <param name="property">The property.</param>
-        /// <returns>An observable.</returns>
-        public static IObservable<object> GetWeakObservable(this IAvaloniaObject o, AvaloniaProperty property)
-        {
-            Contract.Requires<ArgumentNullException>(o != null);
-            Contract.Requires<ArgumentNullException>(property != null);
-
-            return new WeakPropertyChangedObservable(
-                new WeakReference<IAvaloniaObject>(o), 
-                property, 
-                GetDescription(o, property));
-        }
-
         /// <summary>
         /// Binds a property on an <see cref="IAvaloniaObject"/> to an <see cref="IBinding"/>.
         /// </summary>

+ 17 - 42
src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs

@@ -2,13 +2,9 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
-using System.Collections;
-using System.Collections.Generic;
 using System.Collections.Specialized;
-using System.Reactive;
-using System.Reactive.Disposables;
 using System.Reactive.Linq;
-using System.Reactive.Subjects;
+using Avalonia.Reactive;
 using Avalonia.Utilities;
 
 namespace Avalonia.Collections
@@ -43,9 +39,8 @@ namespace Avalonia.Collections
             Contract.Requires<ArgumentNullException>(collection != null);
             Contract.Requires<ArgumentNullException>(handler != null);
 
-            return
-                collection.GetWeakCollectionChangedObservable()
-                          .Subscribe(e => handler.Invoke(collection, e));
+            return collection.GetWeakCollectionChangedObservable()
+                .Subscribe(e => handler(collection, e));
         }
 
         /// <summary>
@@ -63,18 +58,13 @@ namespace Avalonia.Collections
             Contract.Requires<ArgumentNullException>(collection != null);
             Contract.Requires<ArgumentNullException>(handler != null);
 
-            return
-                collection.GetWeakCollectionChangedObservable()
-                          .Subscribe(handler);
+            return collection.GetWeakCollectionChangedObservable().Subscribe(handler);
         }
 
-        private class WeakCollectionChangedObservable : ObservableBase<NotifyCollectionChangedEventArgs>,
+        private class WeakCollectionChangedObservable : LightweightObservableBase<NotifyCollectionChangedEventArgs>,
             IWeakSubscriber<NotifyCollectionChangedEventArgs>
         {
             private WeakReference<INotifyCollectionChanged> _sourceReference;
-            private readonly Subject<NotifyCollectionChangedEventArgs> _changed = new Subject<NotifyCollectionChangedEventArgs>();
-
-            private int _count;
 
             public WeakCollectionChangedObservable(WeakReference<INotifyCollectionChanged> source)
             {
@@ -83,43 +73,28 @@ namespace Avalonia.Collections
 
             public void OnEvent(object sender, NotifyCollectionChangedEventArgs e)
             {
-                _changed.OnNext(e);
+                PublishNext(e);
             }
 
-            protected override IDisposable SubscribeCore(IObserver<NotifyCollectionChangedEventArgs> observer)
+            protected override void Initialize()
             {
                 if (_sourceReference.TryGetTarget(out INotifyCollectionChanged instance))
                 {
-                    if (_count++ == 0)
-                    {
-                        WeakSubscriptionManager.Subscribe(
-                            instance,
-                            nameof(instance.CollectionChanged),
-                            this);
-                    }
-
-                    return Observable.Using(() => Disposable.Create(DecrementCount), _ => _changed)
-                        .Subscribe(observer);
-                }
-                else
-                {
-                    _changed.OnCompleted();
-                    observer.OnCompleted();
-                    return Disposable.Empty;
+                    WeakSubscriptionManager.Subscribe(
+                    instance,
+                    nameof(instance.CollectionChanged),
+                    this);
                 }
             }
 
-            private void DecrementCount()
+            protected override void Deinitialize()
             {
-                if (--_count == 0)
+                if (_sourceReference.TryGetTarget(out INotifyCollectionChanged instance))
                 {
-                    if (_sourceReference.TryGetTarget(out INotifyCollectionChanged instance))
-                    {
-                        WeakSubscriptionManager.Unsubscribe(
-                            instance,
-                            nameof(instance.CollectionChanged),
-                            this);
-                    }
+                    WeakSubscriptionManager.Unsubscribe(
+                        instance,
+                        nameof(instance.CollectionChanged),
+                        this);
                 }
             }
         }

+ 20 - 5
src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs

@@ -2,11 +2,13 @@
 using System.Collections.Generic;
 using System.Reactive.Linq;
 using System.Text;
+using Avalonia.Reactive;
 
 namespace Avalonia.Data.Core
 {
-    public class AvaloniaPropertyAccessorNode : ExpressionNode, ISettableNode
+    public class AvaloniaPropertyAccessorNode : SettableNode
     {
+        private IDisposable _subscription;
         private readonly bool _enableValidation;
         private readonly AvaloniaProperty _property;
 
@@ -18,9 +20,9 @@ namespace Avalonia.Data.Core
 
         public override string Description => PropertyName;
         public string PropertyName { get; }
-        public Type PropertyType => _property.PropertyType;
+        public override Type PropertyType => _property.PropertyType;
 
-        public bool SetTargetValue(object value, BindingPriority priority)
+        protected override bool SetTargetValueCore(object value, BindingPriority priority)
         {
             try
             {
@@ -37,9 +39,22 @@ namespace Avalonia.Data.Core
             }
         }
 
-        protected override IObservable<object> StartListeningCore(WeakReference reference)
+        protected override void StartListeningCore(WeakReference reference)
         {
-            return (reference.Target as IAvaloniaObject)?.GetWeakObservable(_property) ?? Observable.Empty<object>();
+            if (reference.Target is IAvaloniaObject obj)
+            {
+                _subscription = new AvaloniaPropertyObservable<object>(obj, _property).Subscribe(ValueChanged);
+            }
+            else
+            {
+                _subscription = null;
+            }
+        }
+
+        protected override void StopListeningCore()
+        {
+            _subscription?.Dispose();
+            _subscription = null;
         }
     }
 }

+ 38 - 7
src/Avalonia.Base/Data/Core/BindingExpression.cs

@@ -7,21 +7,23 @@ using System.Reactive.Linq;
 using System.Reactive.Subjects;
 using Avalonia.Data.Converters;
 using Avalonia.Logging;
+using Avalonia.Reactive;
 using Avalonia.Utilities;
 
 namespace Avalonia.Data.Core
 {
     /// <summary>
     /// Binds to an expression on an object using a type value converter to convert the values
-    /// that are send and received.
+    /// that are sent and received.
     /// </summary>
-    public class BindingExpression : ISubject<object>, IDescription
+    public class BindingExpression : LightweightObservableBase<object>, ISubject<object>, IDescription
     {
         private readonly ExpressionObserver _inner;
         private readonly Type _targetType;
         private readonly object _fallbackValue;
         private readonly BindingPriority _priority;
-        private readonly Subject<object> _errors = new Subject<object>();
+        InnerListener _innerListener;
+        WeakReference<object> _value;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="ExpressionObserver"/> class.
@@ -139,7 +141,7 @@ namespace Avalonia.Data.Core
                                 "IValueConverter should not return non-errored BindingNotification.");
                         }
 
-                        _errors.OnNext(notification);
+                        PublishNext(notification);
 
                         if (_fallbackValue != AvaloniaProperty.UnsetValue)
                         {
@@ -170,12 +172,18 @@ namespace Avalonia.Data.Core
             }
         }
 
-        /// <inheritdoc/>
-        public IDisposable Subscribe(IObserver<object> observer)
+        protected override void Initialize() => _innerListener = new InnerListener(this);
+        protected override void Deinitialize() => _innerListener.Dispose();
+
+        protected override void Subscribed(IObserver<object> observer, bool first)
         {
-            return _inner.Select(ConvertValue).Merge(_errors).Subscribe(observer);
+            if (!first && _value != null && _value.TryGetTarget(out var val) == true)
+            {
+                observer.OnNext(val);
+            }
         }
 
+        /// <inheritdoc/>
         private object ConvertValue(object value)
         {
             var notification = value as BindingNotification;
@@ -301,5 +309,28 @@ namespace Avalonia.Data.Core
 
             return a;
         }
+
+        public class InnerListener : IObserver<object>, IDisposable
+        {
+            private readonly BindingExpression _owner;
+            private readonly IDisposable _dispose;
+
+            public InnerListener(BindingExpression owner)
+            {
+                _owner = owner;
+                _dispose = owner._inner.Subscribe(this);
+            }
+
+            public void Dispose() => _dispose.Dispose();
+            public void OnCompleted() => _owner.PublishCompleted();
+            public void OnError(Exception error) => _owner.PublishError(error);
+
+            public void OnNext(object value)
+            {
+                var converted = _owner.ConvertValue(value);
+                _owner._value = new WeakReference<object>(converted);
+                _owner.PublishNext(converted);
+            }
+        }
     }
 }

+ 0 - 5
src/Avalonia.Base/Data/Core/EmptyExpressionNode.cs

@@ -9,10 +9,5 @@ namespace Avalonia.Data.Core
     public class EmptyExpressionNode : ExpressionNode
     {
         public override string Description => ".";
-
-        protected override IObservable<object> StartListeningCore(WeakReference reference)
-        {
-            return Observable.Return(reference.Target);
-        }
     }
 }

+ 63 - 61
src/Avalonia.Base/Data/Core/ExpressionNode.cs

@@ -2,21 +2,20 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
-using System.Reactive.Disposables;
-using System.Reactive.Linq;
-using System.Reactive.Subjects;
-using Avalonia.Data;
 
 namespace Avalonia.Data.Core
 {
-    public abstract class ExpressionNode : ISubject<object>
+    public abstract class ExpressionNode
     {
+        private static readonly object CacheInvalid = new object();
         protected static readonly WeakReference UnsetReference = 
             new WeakReference(AvaloniaProperty.UnsetValue);
 
         private WeakReference _target = UnsetReference;
-        private IDisposable _valueSubscription;
-        private IObserver<object> _observer;
+        private Action<object> _subscriber;
+        private bool _listening;
+
+        protected WeakReference LastValue { get; private set; }
 
         public abstract string Description { get; }
         public ExpressionNode Next { get; set; }
@@ -30,119 +29,122 @@ namespace Avalonia.Data.Core
 
                 var oldTarget = _target?.Target;
                 var newTarget = value.Target;
-                var running = _valueSubscription != null;
 
                 if (!ReferenceEquals(oldTarget, newTarget))
                 {
-                    _valueSubscription?.Dispose();
-                    _valueSubscription = null;
+                    if (_listening)
+                    {
+                        StopListening();
+                    }
+
                     _target = value;
 
-                    if (running)
+                    if (_subscriber != null)
                     {
-                        _valueSubscription = StartListening();
+                        StartListening();
                     }
                 }
             }
         }
 
-        public IDisposable Subscribe(IObserver<object> observer)
+        public void Subscribe(Action<object> subscriber)
         {
-            if (_observer != null)
+            if (_subscriber != null)
             {
                 throw new AvaloniaInternalException("ExpressionNode can only be subscribed once.");
             }
 
-            _observer = observer;
-            var nextSubscription = Next?.Subscribe(this);
-            _valueSubscription = StartListening();
-
-            return Disposable.Create(() =>
-            {
-                _valueSubscription?.Dispose();
-                _valueSubscription = null;
-                nextSubscription?.Dispose();
-                _observer = null;
-            });
+            _subscriber = subscriber;
+            Next?.Subscribe(NextValueChanged);
+            StartListening();
         }
 
-        void IObserver<object>.OnCompleted()
+        public void Unsubscribe()
         {
-            throw new AvaloniaInternalException("ExpressionNode.OnCompleted should not be called.");
-        }
+            Next?.Unsubscribe();
 
-        void IObserver<object>.OnError(Exception error)
-        {
-            throw new AvaloniaInternalException("ExpressionNode.OnError should not be called.");
+            if (_listening)
+            {
+                StopListening();
+            }
+
+            LastValue = null;
+            _subscriber = null;
         }
 
-        void IObserver<object>.OnNext(object value)
+        protected virtual void StartListeningCore(WeakReference reference)
         {
-            NextValueChanged(value);
+            ValueChanged(reference.Target);
         }
 
-        protected virtual IObservable<object> StartListeningCore(WeakReference reference)
+        protected virtual void StopListeningCore()
         {
-            return Observable.Return(reference.Target);
         }
 
         protected virtual void NextValueChanged(object value)
         {
             var bindingBroken = BindingNotification.ExtractError(value) as MarkupBindingChainException;
             bindingBroken?.AddNode(Description);
-            _observer.OnNext(value);
+            _subscriber(value);
         }
 
-        private IDisposable StartListening()
-        {
-            var target = _target.Target;
-            IObservable<object> source;
-
-            if (target == null)
-            {
-                source = Observable.Return(TargetNullNotification());
-            }
-            else if (target == AvaloniaProperty.UnsetValue)
-            {
-                source = Observable.Empty<object>();
-            }
-            else
-            {
-                source = StartListeningCore(_target);
-            }
-
-            return source.Subscribe(ValueChanged);
-        }
-
-        private void ValueChanged(object value)
+        protected void ValueChanged(object value)
         {
             var notification = value as BindingNotification;
 
             if (notification == null)
             {
+                LastValue = new WeakReference(value);
                 if (Next != null)
                 {
                     Next.Target = new WeakReference(value);
                 }
                 else
                 {
-                    _observer.OnNext(value);
+                    _subscriber(value);
                 }
             }
             else
             {
+                LastValue = new WeakReference(notification.Value);
+
                 if (Next != null)
                 {
                     Next.Target = new WeakReference(notification.Value);
                 }
-                
+
                 if (Next == null || notification.Error != null)
                 {
-                    _observer.OnNext(value);
+                    _subscriber(value);
                 }
             }
         }
 
+        private void StartListening()
+        {
+            var target = _target.Target;
+
+            if (target == null)
+            {
+                ValueChanged(TargetNullNotification());
+                _listening = false;
+            }
+            else if (target != AvaloniaProperty.UnsetValue)
+            {
+                StartListeningCore(_target);
+                _listening = true;
+            }
+            else
+            {
+                _listening = false;
+            }
+        }
+
+        private void StopListening()
+        {
+            StopListeningCore();
+        }
+
         private BindingNotification TargetNullNotification()
         {
             return new BindingNotification(

+ 91 - 83
src/Avalonia.Base/Data/Core/ExpressionObserver.cs

@@ -5,19 +5,18 @@ using System;
 using System.Collections.Generic;
 using System.Linq.Expressions;
 using System.Reactive;
-using System.Reactive.Disposables;
 using System.Reactive.Linq;
-using System.Reactive.Subjects;
 using Avalonia.Data;
 using Avalonia.Data.Core.Parsers;
 using Avalonia.Data.Core.Plugins;
+using Avalonia.Reactive;
 
 namespace Avalonia.Data.Core
 {
     /// <summary>
     /// Observes and sets the value of an expression on an object.
     /// </summary>
-    public class ExpressionObserver : ObservableBase<object>, IDescription
+    public class ExpressionObserver : LightweightObservableBase<object>, IDescription
     {
         /// <summary>
         /// An ordered collection of property accessor plugins that can be used to customize
@@ -56,9 +55,9 @@ namespace Avalonia.Data.Core
 
         private static readonly object UninitializedValue = new object();
         private readonly ExpressionNode _node;
-        private readonly Subject<Unit> _finished;
-        private readonly object _root;
-        private IObservable<object> _result;
+        private object _root;
+        private IDisposable _rootSubscription;
+        private WeakReference<object> _value;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="ExpressionObserver"/> class.
@@ -66,7 +65,7 @@ namespace Avalonia.Data.Core
         /// <param name="root">The root object.</param>
         /// <param name="node">The expression.</param>
         /// <param name="description">
-        /// A description of the expression. If null, <paramref name="node"/> will be used.
+        /// A description of the expression.
         /// </param>
         public ExpressionObserver(
             object root,
@@ -83,22 +82,13 @@ namespace Avalonia.Data.Core
             _root = new WeakReference(root);
         }
 
-        public static ExpressionObserver Create<T, U>(
-            T root,
-            Expression<Func<T, U>> expression,
-            bool enableDataValidation = false,
-            string description = null)
-        {
-            return new ExpressionObserver(root, Parse(expression, enableDataValidation), description ?? expression.ToString());
-        }
-
         /// <summary>
         /// Initializes a new instance of the <see cref="ExpressionObserver"/> class.
         /// </summary>
         /// <param name="rootObservable">An observable which provides the root object.</param>
         /// <param name="node">The expression.</param>
         /// <param name="description">
-        /// A description of the expression. If null, <paramref name="node"/> will be used.
+        /// A description of the expression.
         /// </param>
         public ExpressionObserver(
             IObservable<object> rootObservable,
@@ -110,20 +100,6 @@ namespace Avalonia.Data.Core
             _node = node;
             Description = description;
             _root = rootObservable;
-            _finished = new Subject<Unit>();
-        }
-
-        public static ExpressionObserver Create<T, U>(
-            IObservable<T> rootObservable,
-            Expression<Func<T, U>> expression,
-            bool enableDataValidation = false,
-            string description = null)
-        {
-            Contract.Requires<ArgumentNullException>(rootObservable != null);
-            return new ExpressionObserver(
-                rootObservable.Select(o => (object)o),
-                Parse(expression, enableDataValidation),
-                description ?? expression.ToString());
         }
 
         /// <summary>
@@ -133,7 +109,7 @@ namespace Avalonia.Data.Core
         /// <param name="node">The expression.</param>
         /// <param name="update">An observable which triggers a re-read of the getter.</param>
         /// <param name="description">
-        /// A description of the expression. If null, <paramref name="node"/> will be used.
+        /// A description of the expression.
         /// </param>
         public ExpressionObserver(
             Func<object> rootGetter,
@@ -143,14 +119,63 @@ namespace Avalonia.Data.Core
         {
             Contract.Requires<ArgumentNullException>(rootGetter != null);
             Contract.Requires<ArgumentNullException>(update != null);
-
             Description = description;
             _node = node;
-            _finished = new Subject<Unit>();
             _node.Target = new WeakReference(rootGetter());
             _root = update.Select(x => rootGetter());
         }
 
+
+        /// <summary>
+        /// Creates a new instance of the <see cref="ExpressionObserver"/> class.
+        /// </summary>
+        /// <param name="root">The root object.</param>
+        /// <param name="expression">The expression.</param>
+        /// <param name="enableDataValidation">Whether or not to track data validation</param>
+        /// <param name="description">
+        /// A description of the expression. If null, <paramref name="expression"/>'s string representation will be used.
+        /// </param>
+        public static ExpressionObserver Create<T, U>(
+            T root,
+            Expression<Func<T, U>> expression,
+            bool enableDataValidation = false,
+            string description = null)
+        {
+            return new ExpressionObserver(root, Parse(expression, enableDataValidation), description ?? expression.ToString());
+        }
+
+        /// <summary>
+        /// Creates a new instance of the <see cref="ExpressionObserver"/> class.
+        /// </summary>
+        /// <param name="rootObservable">An observable which provides the root object.</param>
+        /// <param name="expression">The expression.</param>
+        /// <param name="enableDataValidation">Whether or not to track data validation</param>
+        /// <param name="description">
+        /// A description of the expression. If null, <paramref name="expression"/>'s string representation will be used.
+        /// </param>
+        public static ExpressionObserver Create<T, U>(
+            IObservable<T> rootObservable,
+            Expression<Func<T, U>> expression,
+            bool enableDataValidation = false,
+            string description = null)
+        {
+            Contract.Requires<ArgumentNullException>(rootObservable != null);
+            return new ExpressionObserver(
+                rootObservable.Select(o => (object)o),
+                Parse(expression, enableDataValidation),
+                description ?? expression.ToString());
+        }
+
+        /// <summary>
+        /// Creates a new instance of the <see cref="ExpressionObserver"/> class.
+        /// </summary>
+        /// <param name="rootGetter">A function which gets the root object.</param>
+        /// <param name="expression">The expression.</param>
+        /// <param name="update">An observable which triggers a re-read of the getter.</param>
+        /// <param name="enableDataValidation">Whether or not to track data validation</param>
+        /// <param name="description">
+        /// A description of the expression. If null, <paramref name="expression"/>'s string representation will be used.
+        /// </param>
         public static ExpressionObserver Create<T, U>(
             Func<T> rootGetter,
             Expression<Func<T, U>> expression,
@@ -180,7 +205,7 @@ namespace Avalonia.Data.Core
         /// </returns>
         public bool SetValue(object value, BindingPriority priority = BindingPriority.LocalValue)
         {
-            if (Leaf is ISettableNode settable)
+            if (Leaf is SettableNode settable)
             {
                 var node = _node;
                 while (node != null)
@@ -214,7 +239,7 @@ namespace Avalonia.Data.Core
         /// Gets the type of the expression result or null if the expression could not be 
         /// evaluated.
         /// </summary>
-        public Type ResultType => (Leaf as ISettableNode)?.PropertyType;
+        public Type ResultType => (Leaf as SettableNode)?.PropertyType;
 
         /// <summary>
         /// Gets the leaf node.
@@ -229,71 +254,54 @@ namespace Avalonia.Data.Core
             }
         }
 
-        /// <inheritdoc/>
-        protected override IDisposable SubscribeCore(IObserver<object> observer)
+        protected override void Initialize()
         {
-            if (_result == null)
-            {
-                var source = (IObservable<object>)_node;
-
-                if (_finished != null)
-                {
-                    source = source.TakeUntil(_finished);
-                }
-
-                _result = Observable.Using(StartRoot, _ => source)
-                    .Select(ToWeakReference)
-                    .Publish(UninitializedValue)
-                    .RefCount()
-                    .Where(x => x != UninitializedValue)
-                    .Select(Translate);
-            }
+            _value = null;
+            _node.Subscribe(ValueChanged);
+            StartRoot();
+        }
 
-            return _result.Subscribe(observer);
+        protected override void Deinitialize()
+        {
+            _rootSubscription?.Dispose();
+            _rootSubscription = null;
+            _node.Unsubscribe();
         }
 
-        private static ExpressionNode Parse(LambdaExpression expression, bool enableDataValidation)
+        protected override void Subscribed(IObserver<object> observer, bool first)
         {
-            var parser = new ExpressionTreeParser(enableDataValidation);
-            return parser.Parse(expression);
+            if (!first && _value != null && _value.TryGetTarget(out var value))
+            {
+                observer.OnNext(value);
+            }
         }
 
-        private static object ToWeakReference(object o)
+        private static ExpressionNode Parse(LambdaExpression expression, bool enableDataValidation)
         {
-            return o is BindingNotification ? o : new WeakReference(o);
+            return ExpressionTreeParser.Parse(expression, enableDataValidation);
         }
 
-        private object Translate(object o)
+        private void StartRoot()
         {
-            if (o is WeakReference weak)
+            if (_root is IObservable<object> observable)
             {
-                return weak.Target;
+                _rootSubscription = observable.Subscribe(
+                    x => _node.Target = new WeakReference(x != AvaloniaProperty.UnsetValue ? x : null),
+                    x => PublishCompleted(),
+                    () => PublishCompleted());
             }
-            else if (BindingNotification.ExtractError(o) is MarkupBindingChainException broken)
+            else
             {
-                broken.Commit(Description);
+                _node.Target = (WeakReference)_root;
             }
-
-            return o;
         }
 
-        private IDisposable StartRoot()
+        private void ValueChanged(object value)
         {
-            switch (_root)
-            {
-                case IObservable<object> observable:
-                    return observable.Subscribe(
-                        x => _node.Target = new WeakReference(x != AvaloniaProperty.UnsetValue ? x : null),
-                        _ => _finished.OnNext(Unit.Default),
-                        () => _finished.OnNext(Unit.Default));
-                case WeakReference weak:
-                    _node.Target = weak;
-                    break;
-                default:
-                    throw new AvaloniaInternalException("The ExpressionObserver._root member should only be either an observable or WeakReference.");
-            }
-
-            return Disposable.Empty;
+            var broken = BindingNotification.ExtractError(value) as MarkupBindingChainException;
+            broken?.Commit(Description);
+            _value = new WeakReference<object>(value);
+            PublishNext(value);
         }
     }
 }

+ 0 - 15
src/Avalonia.Base/Data/Core/ISettableNode.cs

@@ -1,15 +0,0 @@
-using Avalonia.Data;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Avalonia.Data.Core
-{
-    interface ISettableNode
-    {
-        bool SetTargetValue(object value, BindingPriority priority);
-        Type PropertyType { get; }
-    }
-}

+ 27 - 17
src/Avalonia.Base/Data/Core/IndexerExpressionNode.cs

@@ -2,6 +2,7 @@
 using System.Collections.Generic;
 using System.ComponentModel;
 using System.Linq.Expressions;
+using System.Reflection;
 using System.Text;
 using Avalonia.Data;
 
@@ -9,35 +10,35 @@ namespace Avalonia.Data.Core
 {
     class IndexerExpressionNode : IndexerNodeBase
     {
-        private readonly ParameterExpression parameter;
-        private readonly IndexExpression expression;
-        private readonly Delegate setDelegate;
-        private readonly Delegate getDelegate;
-        private readonly Delegate firstArgumentDelegate;
+        private readonly ParameterExpression _parameter;
+        private readonly IndexExpression _expression;
+        private readonly Delegate _setDelegate;
+        private readonly Delegate _getDelegate;
+        private readonly Delegate _firstArgumentDelegate;
 
         public IndexerExpressionNode(IndexExpression expression)
         {
-            parameter = Expression.Parameter(expression.Object.Type);
-            this.expression = expression.Update(parameter, expression.Arguments);
+            _parameter = Expression.Parameter(expression.Object.Type);
+            _expression = expression.Update(_parameter, expression.Arguments);
 
-            getDelegate = Expression.Lambda(this.expression, parameter).Compile();
+            _getDelegate = Expression.Lambda(_expression, _parameter).Compile();
 
             var valueParameter = Expression.Parameter(expression.Type);
 
-            setDelegate = Expression.Lambda(Expression.Assign(this.expression, valueParameter), parameter, valueParameter).Compile();
+            _setDelegate = Expression.Lambda(Expression.Assign(_expression, valueParameter), _parameter, valueParameter).Compile();
 
-            firstArgumentDelegate = Expression.Lambda(this.expression.Arguments[0], parameter).Compile();
+            _firstArgumentDelegate = Expression.Lambda(_expression.Arguments[0], _parameter).Compile();
         }
 
-        public override Type PropertyType => expression.Type;
+        public override Type PropertyType => _expression.Type;
 
-        public override string Description => expression.ToString();
+        public override string Description => _expression.ToString();
 
-        public override bool SetTargetValue(object value, BindingPriority priority)
+        protected override bool SetTargetValueCore(object value, BindingPriority priority)
         {
             try
             {
-                setDelegate.DynamicInvoke(Target.Target, value);
+                _setDelegate.DynamicInvoke(Target.Target, value);
                 return true;
             }
             catch (Exception)
@@ -48,14 +49,23 @@ namespace Avalonia.Data.Core
 
         protected override object GetValue(object target)
         {
-            return getDelegate.DynamicInvoke(target);
+            try
+            {
+                return _getDelegate.DynamicInvoke(target);
+            }
+            catch (TargetInvocationException e) when (e.InnerException is ArgumentOutOfRangeException
+                                                        || e.InnerException is IndexOutOfRangeException
+                                                        || e.InnerException is KeyNotFoundException)
+            {
+                return AvaloniaProperty.UnsetValue;
+            }
         }
 
         protected override bool ShouldUpdate(object sender, PropertyChangedEventArgs e)
         {
-            return expression.Indexer.Name == e.PropertyName;
+            return _expression.Indexer == null || _expression.Indexer.Name == e.PropertyName;
         }
 
-        protected override int? TryGetFirstArgumentAsInt() => firstArgumentDelegate.DynamicInvoke(Target.Target) as int?;
+        protected override int? TryGetFirstArgumentAsInt() => _firstArgumentDelegate.DynamicInvoke(Target.Target) as int?;
     }
 }

+ 13 - 8
src/Avalonia.Base/Data/Core/IndexerNodeBase.cs

@@ -13,14 +13,18 @@ using Avalonia.Utilities;
 
 namespace Avalonia.Data.Core
 {
-    public abstract class IndexerNodeBase : ExpressionNode, ISettableNode
+    public abstract class IndexerNodeBase : SettableNode
     {
-        protected override IObservable<object> StartListeningCore(WeakReference reference)
+        private IDisposable _subscription;
+        
+        protected override void StartListeningCore(WeakReference reference)
         {
             var target = reference.Target;
+            var incc = target as INotifyCollectionChanged;
+            var inpc = target as INotifyPropertyChanged;
             var inputs = new List<IObservable<object>>();
 
-            if (target is INotifyCollectionChanged incc)
+            if (incc != null)
             {
                 inputs.Add(WeakObservable.FromEventPattern<INotifyCollectionChanged, NotifyCollectionChangedEventArgs>(
                     incc,
@@ -29,7 +33,7 @@ namespace Avalonia.Data.Core
                     .Select(_ => GetValue(target)));
             }
 
-            if (target is INotifyPropertyChanged inpc)
+            if (inpc != null)
             {
                 inputs.Add(WeakObservable.FromEventPattern<INotifyPropertyChanged, PropertyChangedEventArgs>(
                     inpc,
@@ -38,12 +42,13 @@ namespace Avalonia.Data.Core
                     .Select(_ => GetValue(target)));
             }
 
-            return inputs.Merge().StartWith(GetValue(target));
+            _subscription = Observable.Merge(inputs).StartWith(GetValue(target)).Subscribe(ValueChanged);
         }
 
-        public abstract bool SetTargetValue(object value, BindingPriority priority);
-
-        public abstract Type PropertyType { get; }
+        protected override void StopListeningCore()
+        {
+            _subscription.Dispose();
+        }
 
         protected abstract object GetValue(object target);
 

+ 2 - 9
src/Avalonia.Base/Data/Core/Parsers/ExpressionTreeParser.cs

@@ -6,16 +6,9 @@ using System.Text;
 
 namespace Avalonia.Data.Core.Parsers
 {
-    class ExpressionTreeParser
+    static class ExpressionTreeParser
     {
-        private readonly bool enableDataValidation;
-
-        public ExpressionTreeParser(bool enableDataValidation)
-        {
-            this.enableDataValidation = enableDataValidation;
-        }
-
-        public ExpressionNode Parse(Expression expr)
+        public static ExpressionNode Parse(Expression expr, bool enableDataValidation)
         {
             var visitor = new ExpressionVisitorNodeBuilder(enableDataValidation);
 

+ 20 - 17
src/Avalonia.Base/Data/Core/Parsers/ExpressionVisitorNodeBuilder.cs

@@ -10,10 +10,11 @@ namespace Avalonia.Data.Core.Parsers
 {
     class ExpressionVisitorNodeBuilder : ExpressionVisitor
     {
+        private const string MultiDimensionalArrayGetterMethodName = "Get";
         private static PropertyInfo AvaloniaObjectIndexer;
         private static MethodInfo CreateDelegateMethod;
 
-        private readonly bool enableDataValidation;
+        private readonly bool _enableDataValidation;
 
         static ExpressionVisitorNodeBuilder()
         {
@@ -25,7 +26,7 @@ namespace Avalonia.Data.Core.Parsers
 
         public ExpressionVisitorNodeBuilder(bool enableDataValidation)
         {
-            this.enableDataValidation = enableDataValidation;
+            _enableDataValidation = enableDataValidation;
             Nodes = new List<ExpressionNode>();
         }
 
@@ -61,7 +62,7 @@ namespace Avalonia.Data.Core.Parsers
         protected override Expression VisitMember(MemberExpression node)
         {
             var visited = base.VisitMember(node);
-            Nodes.Add(new PropertyAccessorNode(node.Member.Name, enableDataValidation));
+            Nodes.Add(new PropertyAccessorNode(node.Member.Name, _enableDataValidation));
             return visited;
         }
 
@@ -72,7 +73,7 @@ namespace Avalonia.Data.Core.Parsers
             if (node.Indexer == AvaloniaObjectIndexer)
             {
                 var property = GetArgumentExpressionValue<AvaloniaProperty>(node.Arguments[0]);
-                Nodes.Add(new AvaloniaPropertyAccessorNode(property, enableDataValidation));
+                Nodes.Add(new AvaloniaPropertyAccessorNode(property, _enableDataValidation));
             }
             else
             {
@@ -98,7 +99,6 @@ namespace Avalonia.Data.Core.Parsers
         {
             if (node.NodeType == ExpressionType.ArrayIndex)
             {
-                base.VisitBinary(node);
                 return Visit(Expression.MakeIndex(node.Left, null, new[] { node.Right }));
             }
             throw new ExpressionParseException(0, $"Invalid expression type in binding expression: {node.NodeType}.");
@@ -161,21 +161,13 @@ namespace Avalonia.Data.Core.Parsers
 
         protected override Expression VisitMethodCall(MethodCallExpression node)
         {
-            var property = TryGetPropertyFromMethod(node.Method);
-
-            if (property != null)
-            {
-                return Visit(Expression.MakeIndex(node.Object, property, node.Arguments));
-            }
-
             if (node.Method == CreateDelegateMethod)
             {
                 var visited = Visit(node.Arguments[1]);
-                Nodes.Add(new PropertyAccessorNode(GetArgumentExpressionValue<MethodInfo>(node.Object).Name, enableDataValidation));
-                return visited;
+                Nodes.Add(new PropertyAccessorNode(GetArgumentExpressionValue<MethodInfo>(node.Object).Name, _enableDataValidation));
+                return node;
             }
-
-            if (node.Method.Name == StreamBindingExtensions.StreamBindingName || node.Method.Name.StartsWith(StreamBindingExtensions.StreamBindingName + '`'))
+            else if (node.Method.Name == StreamBindingExtensions.StreamBindingName || node.Method.Name.StartsWith(StreamBindingExtensions.StreamBindingName + '`'))
             {
                 if (node.Method.IsStatic)
                 {
@@ -189,7 +181,18 @@ namespace Avalonia.Data.Core.Parsers
                 return node;
             }
 
-            throw new ExpressionParseException(0, $"Invalid expression type in binding expression: {node.NodeType}.");
+            var property = TryGetPropertyFromMethod(node.Method);
+
+            if (property != null)
+            {
+                return Visit(Expression.MakeIndex(node.Object, property, node.Arguments));
+            }
+            else if (node.Object.Type.IsArray && node.Method.Name == MultiDimensionalArrayGetterMethodName)
+            {
+                return Visit(Expression.MakeIndex(node.Object, null, node.Arguments));
+            }
+
+            throw new ExpressionParseException(0, $"Invalid method call in binding expression: '{node.Method.DeclaringType.AssemblyQualifiedName}.{node.Method.Name}'.");
         }
 
         private PropertyInfo TryGetPropertyFromMethod(MethodInfo method)

+ 5 - 5
src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs

@@ -117,15 +117,15 @@ namespace Avalonia.Data.Core.Plugins
                 return false;
             }
 
-            protected override void Dispose(bool disposing)
+            protected override void SubscribeCore()
             {
-                _subscription?.Dispose();
-                _subscription = null;
+                _subscription = Instance?.GetObservable(_property).Subscribe(PublishValue);
             }
 
-            protected override void SubscribeCore(IObserver<object> observer)
+            protected override void UnsubscribeCore()
             {
-                _subscription = Instance?.GetWeakObservable(_property).Subscribe(observer);
+                _subscription?.Dispose();
+                _subscription = null;
             }
         }
     }

+ 5 - 5
src/Avalonia.Base/Data/Core/Plugins/DataValidatiorBase.cs

@@ -55,13 +55,13 @@ namespace Avalonia.Data.Core.Plugins
         /// <param name="value">The value.</param>
         void IObserver<object>.OnNext(object value) => InnerValueChanged(value);
 
-        /// <inheritdoc/>
-        protected override void Dispose(bool disposing) => _inner.Dispose();
-
         /// <summary>
         /// Begins listening to the inner <see cref="IPropertyAccessor"/>.
         /// </summary>
-        protected override void SubscribeCore(IObserver<object> observer) => _inner.Subscribe(this);
+        protected override void SubscribeCore() => _inner.Subscribe(InnerValueChanged);
+
+        /// <inheritdoc/>
+        protected override void UnsubscribeCore() => _inner.Dispose();
 
         /// <summary>
         /// Called when the inner <see cref="IPropertyAccessor"/> notifies with a new value.
@@ -74,7 +74,7 @@ namespace Avalonia.Data.Core.Plugins
         protected virtual void InnerValueChanged(object value)
         {
             var notification = value as BindingNotification ?? new BindingNotification(value);
-            Observer.OnNext(notification);
+            PublishValue(notification);
         }
     }
 }

+ 2 - 3
src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs

@@ -1,7 +1,6 @@
 // Copyright (c) The Avalonia Project. All rights reserved.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
-using Avalonia.Data;
 using System;
 using System.Reflection;
 
@@ -36,11 +35,11 @@ namespace Avalonia.Data.Core.Plugins
                 }
                 catch (TargetInvocationException ex)
                 {
-                    Observer.OnNext(new BindingNotification(ex.InnerException, BindingErrorType.DataValidationError));
+                    PublishValue(new BindingNotification(ex.InnerException, BindingErrorType.DataValidationError));
                 }
                 catch (Exception ex)
                 {
-                    Observer.OnNext(new BindingNotification(ex, BindingErrorType.DataValidationError));
+                    PublishValue(new BindingNotification(ex, BindingErrorType.DataValidationError));
                 }
 
                 return false;

+ 12 - 2
src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessor.cs

@@ -2,7 +2,6 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
-using Avalonia.Data;
 
 namespace Avalonia.Data.Core.Plugins
 {
@@ -10,7 +9,7 @@ namespace Avalonia.Data.Core.Plugins
     /// Defines an accessor to a property on an object returned by a 
     /// <see cref="IPropertyAccessorPlugin"/>
     /// </summary>
-    public interface IPropertyAccessor : IObservable<object>, IDisposable
+    public interface IPropertyAccessor : IDisposable
     {
         /// <summary>
         /// Gets the type of the property.
@@ -38,5 +37,16 @@ namespace Avalonia.Data.Core.Plugins
         /// True if the property was set; false if the property could not be set.
         /// </returns>
         bool SetValue(object value, BindingPriority priority);
+
+        /// <summary>
+        /// Subscribes to the value of the member.
+        /// </summary>
+        /// <param name="listener">A method that receives the values.</param>
+        void Subscribe(Action<object> listener);
+
+        /// <summary>
+        /// Unsubscribes to the value of the member.
+        /// </summary>
+        void Unsubscribe();
     }
 }

+ 9 - 10
src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs

@@ -5,7 +5,6 @@ using System;
 using System.Collections.Generic;
 using System.ComponentModel;
 using System.Linq;
-using Avalonia.Data;
 using Avalonia.Utilities;
 
 namespace Avalonia.Data.Core.Plugins
@@ -40,43 +39,43 @@ namespace Avalonia.Data.Core.Plugins
             {
                 if (e.PropertyName == _name || string.IsNullOrEmpty(e.PropertyName))
                 {
-                    Observer.OnNext(CreateBindingNotification(Value));
+                    PublishValue(CreateBindingNotification(Value));
                 }
             }
 
-            protected override void Dispose(bool disposing)
+            protected override void SubscribeCore()
             {
-                base.Dispose(disposing);
-
                 var target = _reference.Target as INotifyDataErrorInfo;
 
                 if (target != null)
                 {
-                    WeakSubscriptionManager.Unsubscribe(
+                    WeakSubscriptionManager.Subscribe(
                         target,
                         nameof(target.ErrorsChanged),
                         this);
                 }
+
+                base.SubscribeCore();
             }
 
-            protected override void SubscribeCore(IObserver<object> observer)
+            protected override void UnsubscribeCore()
             {
                 var target = _reference.Target as INotifyDataErrorInfo;
 
                 if (target != null)
                 {
-                    WeakSubscriptionManager.Subscribe(
+                    WeakSubscriptionManager.Unsubscribe(
                         target,
                         nameof(target.ErrorsChanged),
                         this);
                 }
 
-                base.SubscribeCore(observer);
+                base.UnsubscribeCore();
             }
 
             protected override void InnerValueChanged(object value)
             {
-                base.InnerValueChanged(CreateBindingNotification(value));
+                PublishValue(CreateBindingNotification(value));
             }
 
             private BindingNotification CreateBindingNotification(object value)

+ 8 - 8
src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs

@@ -103,7 +103,13 @@ namespace Avalonia.Data.Core.Plugins
                 }
             }
 
-            protected override void Dispose(bool disposing)
+            protected override void SubscribeCore()
+            {
+                SendCurrentValue();
+                SubscribeToChanges();
+            }
+
+            protected override void UnsubscribeCore()
             {
                 var inpc = _reference.Target as INotifyPropertyChanged;
 
@@ -116,18 +122,12 @@ namespace Avalonia.Data.Core.Plugins
                 }
             }
 
-            protected override void SubscribeCore(IObserver<object> observer)
-            {
-                SendCurrentValue();
-                SubscribeToChanges();
-            }
-
             private void SendCurrentValue()
             {
                 try
                 {
                     var value = Value;
-                    Observer.OnNext(value);
+                    PublishValue(value);
                 }
                 catch { }
             }

+ 6 - 2
src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs

@@ -74,14 +74,18 @@ namespace Avalonia.Data.Core.Plugins
 
             public override bool SetValue(object value, BindingPriority priority) => false;
 
-            protected override void SubscribeCore(IObserver<object> observer)
+            protected override void SubscribeCore()
             {
                 try
                 {
-                    Observer.OnNext(Value);
+                    PublishValue(Value);
                 }
                 catch { }
             }
+
+            protected override void UnsubscribeCore()
+            {
+            }
         }
     }
 }

+ 38 - 30
src/Avalonia.Base/Data/Core/Plugins/PropertyAccessorBase.cs

@@ -2,67 +2,75 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
-using Avalonia.Data;
 
 namespace Avalonia.Data.Core.Plugins
 {
     /// <summary>
     /// Defines a default base implementation for a <see cref="IPropertyAccessor"/>.
     /// </summary>
-    /// <remarks>
-    /// <see cref="IPropertyAccessor"/> is an observable that will only be subscribed to one time.
-    /// In addition, the subscription can be disposed by calling <see cref="Dispose()"/> on the
-    /// property accessor itself - this prevents needing to hold two references for a subscription.
-    /// </remarks>
     public abstract class PropertyAccessorBase : IPropertyAccessor
     {
+        private Action<object> _listener;
+
         /// <inheritdoc/>
         public abstract Type PropertyType { get; }
 
         /// <inheritdoc/>
         public abstract object Value { get; }
 
-        /// <summary>
-        /// Stops the subscription.
-        /// </summary>
-        public void Dispose() => Dispose(true);
+        /// <inheritdoc/>
+        public void Dispose()
+        {
+            if (_listener != null)
+            {
+                Unsubscribe();
+            }
+        }
 
         /// <inheritdoc/>
         public abstract bool SetValue(object value, BindingPriority priority);
 
-        /// <summary>
-        /// The currently subscribed observer.
-        /// </summary>
-        protected IObserver<object> Observer { get; private set; }
-
         /// <inheritdoc/>
-        public IDisposable Subscribe(IObserver<object> observer)
+        public void Subscribe(Action<object> listener)
         {
-            Contract.Requires<ArgumentNullException>(observer != null);
+            Contract.Requires<ArgumentNullException>(listener != null);
 
-            if (Observer != null)
+            if (_listener != null)
             {
                 throw new InvalidOperationException(
-                    "A property accessor can be subscribed to only once.");
+                    "A member accessor can be subscribed to only once.");
             }
 
-            Observer = observer;
-            SubscribeCore(observer);
-            return this;
+            _listener = listener;
+            SubscribeCore();
         }
 
+        public void Unsubscribe()
+        {
+            if (_listener == null)
+            {
+                throw new InvalidOperationException(
+                    "The member accessor was not subscribed.");
+            }
+
+            UnsubscribeCore();
+            _listener = null;
+        }
+
+        /// <summary>
+        /// Publishes a value to the listener.
+        /// </summary>
+        /// <param name="value">The value.</param>
+        protected void PublishValue(object value) => _listener?.Invoke(value);
+
         /// <summary>
-        /// Stops listening to the property.
+        /// When overridden in a derived class, begins listening to the member.
         /// </summary>
-        /// <param name="disposing">
-        /// True if the <see cref="Dispose()"/> method was called, false if the object is being
-        /// finalized.
-        /// </param>
-        protected virtual void Dispose(bool disposing) => Observer = null;
+        protected abstract void SubscribeCore();
 
         /// <summary>
-        /// When overridden in a derived class, begins listening to the property.
+        /// When overridden in a derived class, stops listening to the member.
         /// </summary>
-        protected abstract void SubscribeCore(IObserver<object> observer);
+        protected abstract void UnsubscribeCore();
     }
 }

+ 6 - 5
src/Avalonia.Base/Data/Core/Plugins/PropertyError.cs

@@ -1,6 +1,4 @@
 using System;
-using System.Reactive.Disposables;
-using Avalonia.Data;
 
 namespace Avalonia.Data.Core.Plugins
 {
@@ -37,10 +35,13 @@ namespace Avalonia.Data.Core.Plugins
             return false;
         }
 
-        public IDisposable Subscribe(IObserver<object> observer)
+        public void Subscribe(Action<object> listener)
+        {
+            listener(_error);
+        }
+
+        public void Unsubscribe()
         {
-            observer.OnNext(_error);
-            return Disposable.Empty;
         }
     }
 }

+ 23 - 15
src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs

@@ -3,14 +3,12 @@
 
 using System;
 using System.Linq;
-using System.Reactive.Disposables;
 using System.Reactive.Linq;
-using Avalonia.Data;
 using Avalonia.Data.Core.Plugins;
 
 namespace Avalonia.Data.Core
 {
-    public class PropertyAccessorNode : ExpressionNode, ISettableNode
+    public class PropertyAccessorNode : SettableNode
     {
         private readonly bool _enableValidation;
         private IPropertyAccessor _accessor;
@@ -23,19 +21,23 @@ namespace Avalonia.Data.Core
 
         public override string Description => PropertyName;
         public string PropertyName { get; }
-        public Type PropertyType => _accessor?.PropertyType;
+        public override Type PropertyType => _accessor?.PropertyType;
 
-        public bool SetTargetValue(object value, BindingPriority priority)
+        protected override bool SetTargetValueCore(object value, BindingPriority priority)
         {
             if (_accessor != null)
             {
-                try { return _accessor.SetValue(value, priority); } catch { }
+                try
+                {
+                    return _accessor.SetValue(value, priority);
+                }
+                catch { }
             }
 
             return false;
         }
 
-        protected override IObservable<object> StartListeningCore(WeakReference reference)
+        protected override void StartListeningCore(WeakReference reference)
         {
             var plugin = ExpressionObserver.PropertyAccessors.FirstOrDefault(x => x.Match(reference.Target, PropertyName));
             var accessor = plugin?.Start(reference, PropertyName);
@@ -51,14 +53,20 @@ namespace Avalonia.Data.Core
                 }
             }
 
-            // Ensure that _accessor is set for the duration of the subscription.
-            return Observable.Using(
-                () =>
-                {
-                    _accessor = accessor;
-                    return Disposable.Create(() => _accessor = null);
-                },
-                _ => accessor);
+            if (accessor == null)
+            {
+                throw new NotSupportedException(
+                    $"Could not find a matching property accessor for {PropertyName}.");
+            }
+
+            accessor.Subscribe(ValueChanged);
+            _accessor = accessor;
+        }
+
+        protected override void StopListeningCore()
+        {
+            _accessor.Dispose();
+            _accessor = null;
         }
     }
 }

+ 38 - 0
src/Avalonia.Base/Data/Core/SettableNode.cs

@@ -0,0 +1,38 @@
+using Avalonia.Data;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Avalonia.Data.Core
+{
+    public abstract class SettableNode : ExpressionNode
+    {
+        public bool SetTargetValue(object value, BindingPriority priority)
+        {
+            if (ShouldNotSet(value))
+            {
+                return true;
+            }
+            return SetTargetValueCore(value, priority);
+        }
+
+        private bool ShouldNotSet(object value)
+        {
+            if (PropertyType == null)
+            {
+                return false;
+            }
+            if (PropertyType.IsValueType)
+            {
+                return LastValue?.Target != null && LastValue.Target.Equals(value);
+            }
+            return LastValue != null && Object.ReferenceEquals(LastValue?.Target, value);
+        }
+
+        protected abstract bool SetTargetValueCore(object value, BindingPriority priority);
+
+        public abstract Type PropertyType { get; }
+    }
+}

+ 5 - 0
src/Avalonia.Base/Data/Core/StreamBindingExtensions.cs

@@ -14,6 +14,11 @@ namespace Avalonia
             throw new InvalidOperationException("This should be used only in a binding expression");
         }
 
+        public static object StreamBinding(this Task @this)
+        {
+            throw new InvalidOperationException("This should be used only in a binding expression");
+        }
+
         public static T StreamBinding<T>(this IObservable<T> @this)
         {
             throw new InvalidOperationException("This should be used only in a binding expression");

+ 12 - 5
src/Avalonia.Base/Data/Core/StreamNode.cs

@@ -2,30 +2,37 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
-using System.Globalization;
-using Avalonia.Data;
 using System.Reactive.Linq;
 
 namespace Avalonia.Data.Core
 {
     public class StreamNode : ExpressionNode
     {
+        private IDisposable _subscription;
+
         public override string Description => "^";
 
-        protected override IObservable<object> StartListeningCore(WeakReference reference)
+        protected override void StartListeningCore(WeakReference reference)
         {
             foreach (var plugin in ExpressionObserver.StreamHandlers)
             {
                 if (plugin.Match(reference))
                 {
-                    return plugin.Start(reference);
+                    _subscription = plugin.Start(reference).Subscribe(ValueChanged);
+                    return;
                 }
             }
 
             // TODO: Improve error.
-            return Observable.Return(new BindingNotification(
+            ValueChanged(new BindingNotification(
                 new MarkupBindingChainException("Stream operator applied to unsupported type", Description),
                 BindingErrorType.Error));
         }
+
+        protected override void StopListeningCore()
+        {
+            _subscription?.Dispose();
+            _subscription = null;
+        }
     }
 }

+ 5 - 4
src/Avalonia.Base/IPriorityValueOwner.cs

@@ -13,18 +13,19 @@ namespace Avalonia
         /// <summary>
         /// Called when a <see cref="PriorityValue"/>'s value changes.
         /// </summary>
-        /// <param name="sender">The source of the change.</param>
+        /// <param name="property">The the property that has changed.</param>
+        /// <param name="priority">The priority of the value.</param>
         /// <param name="oldValue">The old value.</param>
         /// <param name="newValue">The new value.</param>
-        void Changed(PriorityValue sender, object oldValue, object newValue);
+        void Changed(AvaloniaProperty property, int priority, object oldValue, object newValue);
 
         /// <summary>
         /// Called when a <see cref="BindingNotification"/> is received by a 
         /// <see cref="PriorityValue"/>.
         /// </summary>
-        /// <param name="sender">The source of the change.</param>
+        /// <param name="property">The the property that has changed.</param>
         /// <param name="notification">The notification.</param>
-        void BindingNotificationReceived(PriorityValue sender, BindingNotification notification);
+        void BindingNotificationReceived(AvaloniaProperty property, BindingNotification notification);
 
         /// <summary>
         /// Ensures that the current thread is the UI thread.

+ 2 - 2
src/Avalonia.Base/PriorityValue.cs

@@ -281,12 +281,12 @@ namespace Avalonia
 
                 if (notification == null || notification.HasValue)
                 {
-                    notify(() => Owner?.Changed(this, old, Value));
+                    notify(() => Owner?.Changed(Property, ValuePriority, old, Value));
                 }
 
                 if (notification != null)
                 {
-                    Owner?.BindingNotificationReceived(this, notification);
+                    Owner?.BindingNotificationReceived(Property, notification);
                 }
             }
             else

+ 0 - 42
src/Avalonia.Base/Reactive/AvaloniaObservable.cs

@@ -1,42 +0,0 @@
-// Copyright (c) The Avalonia Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-using System;
-using System.Reactive;
-using System.Reactive.Disposables;
-
-namespace Avalonia.Reactive
-{
-    /// <summary>
-    /// An <see cref="IObservable{T}"/> with an additional description.
-    /// </summary>
-    /// <typeparam name="T">The type of the elements in the sequence.</typeparam>
-    public class AvaloniaObservable<T> : ObservableBase<T>, IDescription
-    {
-        private readonly Func<IObserver<T>, IDisposable> _subscribe;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="AvaloniaObservable{T}"/> class.
-        /// </summary>
-        /// <param name="subscribe">The subscribe function.</param>
-        /// <param name="description">The description of the observable.</param>
-        public AvaloniaObservable(Func<IObserver<T>, IDisposable> subscribe, string description)
-        {
-            Contract.Requires<ArgumentNullException>(subscribe != null);            
-
-            _subscribe = subscribe;
-            Description = description;
-        }
-
-        /// <summary>
-        /// Gets the description of the observable.
-        /// </summary>
-        public string Description { get; }
-
-        /// <inheritdoc/>
-        protected override IDisposable SubscribeCore(IObserver<T> observer)
-        {
-            return _subscribe(observer) ?? Disposable.Empty;
-        }
-    }
-}

+ 46 - 0
src/Avalonia.Base/Reactive/AvaloniaPropertyChangedObservable.cs

@@ -0,0 +1,46 @@
+using System;
+
+namespace Avalonia.Reactive
+{
+    internal class AvaloniaPropertyChangedObservable : 
+        LightweightObservableBase<AvaloniaPropertyChangedEventArgs>,
+        IDescription
+    {
+        private readonly WeakReference<IAvaloniaObject> _target;
+        private readonly AvaloniaProperty _property;
+
+        public AvaloniaPropertyChangedObservable(
+            IAvaloniaObject target,
+            AvaloniaProperty property)
+        {
+            _target = new WeakReference<IAvaloniaObject>(target);
+            _property = property;
+        }
+
+        public string Description => $"{_target.GetType().Name}.{_property.Name}";
+
+        protected override void Initialize()
+        {
+            if (_target.TryGetTarget(out var target))
+            {
+                target.PropertyChanged += PropertyChanged;
+            }
+        }
+
+        protected override void Deinitialize()
+        {
+            if (_target.TryGetTarget(out var target))
+            {
+                target.PropertyChanged -= PropertyChanged;
+            }
+        }
+
+        private void PropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
+        {
+            if (e.Property == _property)
+            {
+                PublishNext(e);
+            }
+        }
+    }
+}

+ 52 - 0
src/Avalonia.Base/Reactive/AvaloniaPropertyObservable.cs

@@ -0,0 +1,52 @@
+using System;
+
+namespace Avalonia.Reactive
+{
+    internal class AvaloniaPropertyObservable<T> : LightweightObservableBase<T>, IDescription
+    {
+        private readonly WeakReference<IAvaloniaObject> _target;
+        private readonly AvaloniaProperty _property;
+        private T _value;
+
+        public AvaloniaPropertyObservable(
+            IAvaloniaObject target,
+            AvaloniaProperty property)
+        {
+            _target = new WeakReference<IAvaloniaObject>(target);
+            _property = property;
+        }
+
+        public string Description => $"{_target.GetType().Name}.{_property.Name}";
+
+        protected override void Initialize()
+        {
+            if (_target.TryGetTarget(out var target))
+            {
+                _value = (T)target.GetValue(_property);
+                target.PropertyChanged += PropertyChanged;
+            }
+        }
+
+        protected override void Deinitialize()
+        {
+            if (_target.TryGetTarget(out var target))
+            {
+                target.PropertyChanged -= PropertyChanged;
+            }
+        }
+
+        protected override void Subscribed(IObserver<T> observer, bool first)
+        {
+            observer.OnNext(_value);
+        }
+
+        private void PropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
+        {
+            if (e.Property == _property)
+            {
+                _value = (T)e.NewValue;
+                PublishNext(_value);
+            }
+        }
+    }
+}

+ 202 - 0
src/Avalonia.Base/Reactive/LightweightObservableBase.cs

@@ -0,0 +1,202 @@
+using System;
+using System.Collections.Generic;
+using System.Reactive;
+using System.Reactive.Disposables;
+using System.Threading;
+using Avalonia.Threading;
+
+namespace Avalonia.Reactive
+{
+    /// <summary>
+    /// Lightweight base class for observable implementations.
+    /// </summary>
+    /// <typeparam name="T">The observable type.</typeparam>
+    /// <remarks>
+    /// <see cref="ObservableBase{T}"/> is rather heavyweight in terms of allocations and memory
+    /// usage. This class provides a more lightweight base for some internal observable types
+    /// in the Avalonia framework.
+    /// </remarks>
+    public abstract class LightweightObservableBase<T> : IObservable<T>
+    {
+        private Exception _error;
+        private List<IObserver<T>> _observers = new List<IObserver<T>>();
+
+        public IDisposable Subscribe(IObserver<T> observer)
+        {
+            Contract.Requires<ArgumentNullException>(observer != null);
+            Dispatcher.UIThread.VerifyAccess();
+
+            var first = false;
+
+            for (; ; )
+            {
+                if (Volatile.Read(ref _observers) == null)
+                {
+                    if (_error != null)
+                    {
+                        observer.OnError(_error);
+                    }
+                    else
+                    {
+                        observer.OnCompleted();
+                    }
+
+                    return Disposable.Empty;
+                }
+
+                lock (this)
+                {
+                    if (_observers == null)
+                    {
+                        continue;
+                    }
+
+                    first = _observers.Count == 0;
+                    _observers.Add(observer);
+                    break;
+                }
+            }
+
+            if (first)
+            {
+                Initialize();
+            }
+
+            Subscribed(observer, first);
+
+            return new RemoveObserver(this, observer);
+        }
+
+        void Remove(IObserver<T> observer)
+        {
+            if (Volatile.Read(ref _observers) != null)
+            {
+                lock (this)
+                {
+                    var observers = _observers;
+
+                    if (observers != null)
+                    {
+                        observers.Remove(observer);
+
+                        if (observers.Count == 0)
+                        {
+                            observers.TrimExcess();
+                        }
+                        else
+                        {
+                            return;
+                        }
+                    } else
+                    {
+                        return;
+                    }
+                }
+
+                Deinitialize();
+            }
+        }
+
+        sealed class RemoveObserver : IDisposable
+        {
+            LightweightObservableBase<T> _parent;
+
+            IObserver<T> _observer;
+
+            public RemoveObserver(LightweightObservableBase<T> parent, IObserver<T> observer)
+            {
+                _parent = parent;
+                Volatile.Write(ref _observer, observer);
+            }
+
+            public void Dispose()
+            {
+                var observer = _observer;
+                Interlocked.Exchange(ref _parent, null)?.Remove(observer);
+                _observer = null;
+            }
+        }
+
+        protected abstract void Initialize();
+        protected abstract void Deinitialize();
+
+        protected void PublishNext(T value)
+        {
+            if (Volatile.Read(ref _observers) != null)
+            {
+                IObserver<T>[] observers;
+
+                lock (this)
+                {
+                    if (_observers == null)
+                    {
+                        return;
+                    }
+                    observers = _observers.ToArray();
+                }
+
+                foreach (var observer in observers)
+                {
+                    observer.OnNext(value);
+                }
+            }
+        }
+
+        protected void PublishCompleted()
+        {
+            if (Volatile.Read(ref _observers) != null)
+            {
+                IObserver<T>[] observers;
+
+                lock (this)
+                {
+                    if (_observers == null)
+                    {
+                        return;
+                    }
+                    observers = _observers.ToArray();
+                    Volatile.Write(ref _observers, null);
+                }
+
+                foreach (var observer in observers)
+                {
+                    observer.OnCompleted();
+                }
+
+                Deinitialize();
+            }
+        }
+
+        protected void PublishError(Exception error)
+        {
+            if (Volatile.Read(ref _observers) != null)
+            {
+
+                IObserver<T>[] observers;
+
+                lock (this)
+                {
+                    if (_observers == null)
+                    {
+                        return;
+                    }
+
+                    _error = error;
+                    observers = _observers.ToArray();
+                    Volatile.Write(ref _observers, null);
+                }
+
+                foreach (var observer in observers)
+                {
+                    observer.OnError(error);
+                }
+
+                Deinitialize();
+            }
+        }
+
+        protected virtual void Subscribed(IObserver<T> observer, bool first)
+        {
+        }
+    }
+}

+ 76 - 0
src/Avalonia.Base/Reactive/SingleSubscriberObservableBase.cs

@@ -0,0 +1,76 @@
+using System;
+using Avalonia.Threading;
+
+namespace Avalonia.Reactive
+{
+    public abstract class SingleSubscriberObservableBase<T> : IObservable<T>, IDisposable
+    {
+        private Exception _error;
+        private IObserver<T> _observer;
+        private bool _completed;
+
+        public IDisposable Subscribe(IObserver<T> observer)
+        {
+            Contract.Requires<ArgumentNullException>(observer != null);
+            Dispatcher.UIThread.VerifyAccess();
+
+            if (_observer != null)
+            {
+                throw new InvalidOperationException("The observable can only be subscribed once.");
+            }
+
+            if (_error != null)
+            {
+                observer.OnError(_error);
+            }
+            else if (_completed)
+            {
+                observer.OnCompleted();
+            }
+            else
+            {
+                _observer = observer;
+                Subscribed();
+            }
+
+            return this;
+        }
+
+        void IDisposable.Dispose()
+        {
+            Unsubscribed();
+            _observer = null;
+        }
+
+        protected abstract void Unsubscribed();
+
+        protected void PublishNext(T value)
+        {
+            _observer?.OnNext(value);
+        }
+
+        protected void PublishCompleted()
+        {
+            if (_observer != null)
+            {
+                _observer.OnCompleted();
+                _completed = true;
+                Unsubscribed();
+                _observer = null;
+            }
+        }
+
+        protected void PublishError(Exception error)
+        {
+            if (_observer != null)
+            {
+                _observer.OnError(error);
+                _error = error;
+                Unsubscribed();
+                _observer = null;
+            }
+        }
+
+        protected abstract void Subscribed();
+    }
+}

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.