From 9d9140eeb64e0e9ab1d5e79c356ec3691845b630 Mon Sep 17 00:00:00 2001 From: Stephane Delcroix Date: Sat, 24 Nov 2018 11:08:15 +0100 Subject: [PATCH] [C] fallback nicely in case of IndexOutOfRangeEx (#4525) This PR does 3 things: - In case of Binding path with indexer, lookout for IndexOutOfRangeException in addition to KeyNotFoundException. - in case of KeyNotFound or IndexOutOfRange, use the FallbackValue if any. - in case of an uncaught exception, throw that exception instead of a TargetInvocationException. - fixes #4516 --- Xamarin.Forms.Core/BindingExpression.cs | 15 ++++---- Xamarin.Forms.Core/TypedBinding.cs | 2 +- .../Issues/Gh4516.xaml | 8 +++++ .../Issues/Gh4516.xaml.cs | 36 +++++++++++++++++++ 4 files changed, 53 insertions(+), 8 deletions(-) create mode 100644 Xamarin.Forms.Xaml.UnitTests/Issues/Gh4516.xaml create mode 100644 Xamarin.Forms.Xaml.UnitTests/Issues/Gh4516.xaml.cs diff --git a/Xamarin.Forms.Core/BindingExpression.cs b/Xamarin.Forms.Core/BindingExpression.cs index 7a99cf161..c733d5bcb 100644 --- a/Xamarin.Forms.Core/BindingExpression.cs +++ b/Xamarin.Forms.Core/BindingExpression.cs @@ -610,15 +610,16 @@ namespace Xamarin.Forms { if (IsIndexer) { - try - { + try { value = LastGetter.Invoke(value, Arguments); } - catch (TargetInvocationException ex) - { - if (!(ex.InnerException is KeyNotFoundException)) - throw; - value = null; + catch (TargetInvocationException ex) { + if (ex.InnerException is KeyNotFoundException || ex.InnerException is IndexOutOfRangeException) { + value = null; + return false; + } + else + throw ex.InnerException; } return true; } diff --git a/Xamarin.Forms.Core/TypedBinding.cs b/Xamarin.Forms.Core/TypedBinding.cs index 185590f2f..c8afff741 100644 --- a/Xamarin.Forms.Core/TypedBinding.cs +++ b/Xamarin.Forms.Core/TypedBinding.cs @@ -197,7 +197,7 @@ namespace Xamarin.Forms.Internals if (isTSource) { try { value = GetSourceValue(_getter((TSource)sourceObject), property.ReturnType); - } catch (Exception ex) when (ex is NullReferenceException || ex is KeyNotFoundException) { + } catch (Exception ex) when (ex is NullReferenceException || ex is KeyNotFoundException || ex is IndexOutOfRangeException) { } } if (!BindingExpression.TryConvert(ref value, property, property.ReturnType, true)) { diff --git a/Xamarin.Forms.Xaml.UnitTests/Issues/Gh4516.xaml b/Xamarin.Forms.Xaml.UnitTests/Issues/Gh4516.xaml new file mode 100644 index 000000000..06823a476 --- /dev/null +++ b/Xamarin.Forms.Xaml.UnitTests/Issues/Gh4516.xaml @@ -0,0 +1,8 @@ + + + + diff --git a/Xamarin.Forms.Xaml.UnitTests/Issues/Gh4516.xaml.cs b/Xamarin.Forms.Xaml.UnitTests/Issues/Gh4516.xaml.cs new file mode 100644 index 000000000..47d591d32 --- /dev/null +++ b/Xamarin.Forms.Xaml.UnitTests/Issues/Gh4516.xaml.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using NUnit.Framework; +using Xamarin.Forms; +using Xamarin.Forms.Core.UnitTests; + +namespace Xamarin.Forms.Xaml.UnitTests +{ + public class Gh4516VM { + public Uri[] Images { get; } = { }; + } + + public partial class Gh4516 : ContentPage + { + public Gh4516() => InitializeComponent(); + public Gh4516(bool useCompiledXaml) + { + //this stub will be replaced at compile time + } + + [TestFixture] + class Tests + { + [SetUp] public void Setup() => Device.PlatformServices = new MockPlatformServices(); + [TearDown] public void TearDown() => Device.PlatformServices = null; + + [TestCase(true), TestCase(false)] + public void BindingToEmptyCollection(bool useCompiledXaml) + { + Gh4516 layout = null; + Assert.DoesNotThrow(() => layout = new Gh4516(useCompiledXaml) { BindingContext = new Gh4516VM() }); + Assert.That((layout.image.Source as FileImageSource).File, Is.EqualTo("foo.jpg")); + } + } + } +} \ No newline at end of file