From 12b66e822c5b117ee0a63c560f34974347d0c903 Mon Sep 17 00:00:00 2001 From: RomanFQ Date: Fri, 15 Jun 2012 09:36:53 +0100 Subject: [PATCH] Fixing event raiser with nullable arguments EventRaiser.AssertMatchingParameters ignores null as a valid value for nullable types hence throws with a message similar to this "Parameter #4 is null but should be System.Nullable`1[System.Double]" --- .../FieldsProblem/UsingEvents.cs | 27 ++ Rhino.Mocks/Impl/EventRaiser.cs | 295 +++++++++--------- 2 files changed, 180 insertions(+), 142 deletions(-) diff --git a/Rhino.Mocks.Tests/FieldsProblem/UsingEvents.cs b/Rhino.Mocks.Tests/FieldsProblem/UsingEvents.cs index 491345ce..8f7d3de6 100644 --- a/Rhino.Mocks.Tests/FieldsProblem/UsingEvents.cs +++ b/Rhino.Mocks.Tests/FieldsProblem/UsingEvents.cs @@ -248,6 +248,28 @@ public void UsingEventRaiserFromExtensionMethod() } #endif + [Fact] + public void UsingEventRaiserWithNullableArgumentInEvent() + { + IWithNullableArgsEvents eventHolder = (IWithNullableArgsEvents)mocks.Stub(typeof(IWithNullableArgsEvents)); + IEventRaiser eventRaiser = eventHolder.GetEventRaiser(stub => stub.Foo += null); + + mocks.ReplayAll(); + + bool called = false; + eventHolder.Foo += delegate + { + called = true; + }; + + + eventRaiser.Raise(default(int?)); + + mocks.VerifyAll(); + + Assert.True(called); + } + } public interface IWithEvents @@ -276,4 +298,9 @@ public void RaiseEvent() #endregion } + public interface IWithNullableArgsEvents + { + event Action Foo; + void RaiseEvent(); + } } diff --git a/Rhino.Mocks/Impl/EventRaiser.cs b/Rhino.Mocks/Impl/EventRaiser.cs index cef74f7f..9028b49c 100644 --- a/Rhino.Mocks/Impl/EventRaiser.cs +++ b/Rhino.Mocks/Impl/EventRaiser.cs @@ -1,142 +1,153 @@ -#region license -// Copyright (c) 2005 - 2007 Ayende Rahien (ayende@ayende.com) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, -// are permitted provided that the following conditions are met: -// -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// * Neither the name of Ayende Rahien nor the names of its -// contributors may be used to endorse or promote products derived from this -// software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -#endregion - - -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Text; -using Rhino.Mocks.Interfaces; -using Rhino.Mocks.Utilities; - -namespace Rhino.Mocks.Impl -{ - /// - /// Raise events for all subscribers for an event - /// - public class EventRaiser : IEventRaiser - { - string eventName; - IMockedObject proxy; - - /// - /// Create an event raiser for the specified event on this instance. - /// - public static IEventRaiser Create(object instance, string eventName) - { - IMockedObject proxy = instance as IMockedObject; - if (proxy == null) - throw new ArgumentException("Parameter must be a mocked object", "instance"); - return new EventRaiser(proxy, eventName); - } - - /// - /// Creates a new instance of EventRaiser - /// - public EventRaiser(IMockedObject proxy, string eventName) - { - this.eventName = eventName; - this.proxy = proxy; - } - - #region IEventRaiser Members - - /// - /// Raise the event - /// - public void Raise(params object[] args) - { - Delegate subscribed = proxy.GetEventSubscribers(eventName); - if (subscribed != null) - { - AssertMatchingParameters(subscribed.Method, args); - try - { - subscribed.DynamicInvoke(args); - } - catch (TargetInvocationException e) - { - PreserveStackTrace(e.InnerException); - throw e.InnerException; - } - } - } - - private static void PreserveStackTrace(Exception exception) - { - MethodInfo preserveStackTrace = typeof(Exception).GetMethod("InternalPreserveStackTrace", - BindingFlags.Instance | BindingFlags.NonPublic); - preserveStackTrace.Invoke(exception, null); - } - - private static void AssertMatchingParameters(MethodInfo method, object[] args) - { - ParameterInfo[] parameterInfos = method.GetParameters(); - int paramsCount = parameterInfos.Length; - if(args== null || args.Length != paramsCount) - { - int actualCount; - if(args==null) - actualCount = 0; - else - actualCount = args.Length; - string msg = string.Format("You have called the event raiser with the wrong number of parameters. Expected {0} but was {1}", paramsCount, actualCount); - throw new InvalidOperationException(msg); - } - List errors = new List(); - for (int i = 0; i < parameterInfos.Length; i++) - { - if ((args[i] == null && parameterInfos[i].ParameterType.IsValueType) || - (args[i] != null && parameterInfos[i].ParameterType.IsInstanceOfType(args[i])==false)) - { - string type = "null"; - if(args[i]!=null) - type = args[i].GetType().FullName; - errors.Add("Parameter #" + (i+1) + " is " + type + " but should be " + - parameterInfos[i].ParameterType); - } - } - if(errors.Count>0) - { - throw new InvalidOperationException(string.Join(Environment.NewLine, errors.ToArray())); - } - } - - /// - /// The most common signature for events - /// Here to allow intellisense to make better guesses about how - /// it should suggest parameters. - /// - public void Raise(object sender, EventArgs e) - { - Raise(new object[] { sender, e }); - } - - #endregion - } -} +#region license +// Copyright (c) 2005 - 2007 Ayende Rahien (ayende@ayende.com) +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of Ayende Rahien nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#endregion + + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; +using Rhino.Mocks.Interfaces; +using Rhino.Mocks.Utilities; + +namespace Rhino.Mocks.Impl +{ + /// + /// Raise events for all subscribers for an event + /// + public class EventRaiser : IEventRaiser + { + string eventName; + IMockedObject proxy; + + /// + /// Create an event raiser for the specified event on this instance. + /// + public static IEventRaiser Create(object instance, string eventName) + { + IMockedObject proxy = instance as IMockedObject; + if (proxy == null) + throw new ArgumentException("Parameter must be a mocked object", "instance"); + return new EventRaiser(proxy, eventName); + } + + /// + /// Creates a new instance of EventRaiser + /// + public EventRaiser(IMockedObject proxy, string eventName) + { + this.eventName = eventName; + this.proxy = proxy; + } + + #region IEventRaiser Members + + /// + /// Raise the event + /// + public void Raise(params object[] args) + { + Delegate subscribed = proxy.GetEventSubscribers(eventName); + if (subscribed != null) + { + AssertMatchingParameters(subscribed.Method, args); + try + { + subscribed.DynamicInvoke(args); + } + catch (TargetInvocationException e) + { + PreserveStackTrace(e.InnerException); + throw e.InnerException; + } + } + } + + private static void PreserveStackTrace(Exception exception) + { + MethodInfo preserveStackTrace = typeof(Exception).GetMethod("InternalPreserveStackTrace", + BindingFlags.Instance | BindingFlags.NonPublic); + preserveStackTrace.Invoke(exception, null); + } + + private static void AssertMatchingParameters(MethodInfo method, object[] args) + { + ParameterInfo[] parameterInfos = method.GetParameters(); + int paramsCount = parameterInfos.Length; + if(args== null || args.Length != paramsCount) + { + int actualCount; + if(args==null) + actualCount = 0; + else + actualCount = args.Length; + string msg = string.Format("You have called the event raiser with the wrong number of parameters. Expected {0} but was {1}", paramsCount, actualCount); + throw new InvalidOperationException(msg); + } + List errors = new List(); + for (int i = 0; i < parameterInfos.Length; i++) + { + if ((args[i] == null && !NullIsValidValueFor(parameterInfos[i].ParameterType)) || + (args[i] != null && parameterInfos[i].ParameterType.IsInstanceOfType(args[i])==false)) + { + string type = "null"; + if(args[i]!=null) + type = args[i].GetType().FullName; + errors.Add("Parameter #" + (i+1) + " is " + type + " but should be " + + parameterInfos[i].ParameterType); + } + } + if(errors.Count>0) + { + throw new InvalidOperationException(string.Join(Environment.NewLine, errors.ToArray())); + } + } + + private static bool NullIsValidValueFor(Type type) + { + if (!type.IsValueType) + return true; + + if (Nullable.GetUnderlyingType(type) != null) + return true; + + return false; + } + + /// + /// The most common signature for events + /// Here to allow intellisense to make better guesses about how + /// it should suggest parameters. + /// + public void Raise(object sender, EventArgs e) + { + Raise(new object[] { sender, e }); + } + + #endregion + } +}