Updating a property on one class from other classes while multithreading - c#

I am using WPF and MVVM pattern, but that's not really relevant in this case. In my ViewModel MainVM, I have a property like this:
public int Posts {
get { return this.posts; }
set {
this.posts = value;
this.OnPropertyChanged(nameof(this.Posts));
}
}
Through properties, I supply MainVM a list of Foo. (Actually, I want to supply it using the constructor, but I can't. Read below) And there is a start method on the MainVM which does as follows:
foreach(Foo foo in this.Foos){ // this.Foos is just a List<Foo>
Task.Run(() => foo.Bar());
}
In the execution of Foo.Bar (There are lots of methods being called in Bar, so passing an Action to bar itself is not really feasible), it is supposed to update a value, and that value is MainVM.Posts. Passing MainVM to Foo is not an option. So I thought of passing an Action instead. So I wrote this Method in MainVM.
public void IncrementPosts()
{
lock(this.whatever)
{
this.Posts++;
}
}
Then I made Foo take an Action in the constructor. So here's how the Foos are created and passed to MainVM.
var vm = new MainVM();
var foo1 = new Foo(vm.IncrementPosts);
var foo2 = new Foo(vm.IncrementPosts);
vm.Foos = new List<Foo>() {foo1, foo2};
Whilst this works, I feel that it looks ugly. First of all, MainVM needs a list of Foo to work properly. So it's supposed to be supplied to the constructor, right? But as a Foo needs an action which points to a method in MainVM, I can't do that. And all of this looks like a hack.
Is there a better way of doing this?

If I understand, what are you want to do..
void Main()
{
var vm = new MainVM(new FooFactory());
Console.WriteLine(vm.Posts);
vm.ExecuteFoo();
Console.WriteLine(vm.Posts);
}
public class Foo
{
private Action _action;
public Foo(Action action)
{
_action = action;
}
public void RunAction()
{
_action();
}
}
public class FooFactory
{
public Foo[] CreateFoo(MainVM vm)
{
return new[] { new Foo(()=>vm.Posts++) };
}
}
public class MainVM : INotifyPropertyChanged
{
public MainVM(FooFactory factory)
{
_foos = factory.CreateFoo(this);
}
public void ExecuteFoo()
{
foreach(var foo in _foos)
foo.RunAction();
}
private Foo[] _foos;
private int posts;
public int Posts
{
get { return this.posts; }
set
{
this.posts = value;
this.OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
var local = PropertyChanged;
if (local != null)
{
local(this, new PropertyChangedEventArgs(propertyName));
}
}
}

Related

Mocking a repository that uses EF using Moq, preserving behavior of ContextTracker

So I have a generic repository like this:
using System.Collections.Generic;
using System.Data.Entity;
using System.Threading.Tasks;
namespace TrackIt.UI.Data.Repositories
{
public class GenericRepository<TEntity, TContext> : IGenericRepository<TEntity>
where TContext: DbContext
where TEntity: class
{
protected readonly TContext Context;
protected GenericRepository(TContext context)
{
this.Context = context;
}
public virtual async Task<TEntity> GetByIdAsync(int id)
{
return await Context.Set<TEntity>().FindAsync(id);
}
public bool HasChanges()
{
return Context.ChangeTracker.HasChanges();
}
}
}
And a FriendRepository that inherits from it, with some of its own methods:
using System.Data.Entity;
using System.Threading.Tasks;
using TrackIt.DataAccess;
using TrackIt.Model;
namespace TrackIt.UI.Data.Repositories
{
public class FriendRepository : GenericRepository<Friend, TrackItDbContext>,
IFriendRepository
{
public FriendRepository(TrackItDbContext context): base(context)
{
}
public override async Task<Friend> GetByIdAsync(int id)
{
return await Context.Friends.Include(f => f.PhoneNumbers).SingleAsync(f => f.Id == id);
}
}
}
where TrackItDbContext is just a class inheriting from DbContext, where it has properties that "maps" to the database's tables.
Then, I have an implementing class like this:
namespace TrackIt.UI.ViewModel
{
public class FriendDetailViewModel : DetailViewModelBase, IFriendDetailViewModel
{
public bool HasChanges
{
get { return _hasChanges; }
set
{
if(_hasChanges != value)
{
_hasChanges = value;
OnPropertyChanged();
((DelegateCommand)SaveCommand).RaiseCanExecuteChanged();
}
}
}
private Friend _friend;
public Friend Friend
{
get { return _friend; }
private set
{
_friend = value;
OnPropertyChanged();
}
}
private ICommand _addPhoneNumberCommand;
public ICommand AddPhoneNumberCommand
{
get { return _addPhoneNumberCommand; }
set { _addPhoneNumberCommand = value; }
}
public ObservableCollection<FriendPhoneNumberDecorator> PhoneNumbers { get; }
private IFriendRepository _friendRepository;
private IMessageDialogService _messageDialogServiceProvider;
public FriendDetailViewModel(IFriendRepository friendRepository,
IEventAggregator eventAggregator,
IMessageDialogService messageDialogServiceProvider) : base(eventAggregator)
{
_friendRepository = friendRepository;
_messageDialogServiceProvider = messageDialogServiceProvider;
PhoneNumbers = new ObservableCollection<FriendPhoneNumberDecorator>();
AddPhoneNumberCommand = new DelegateCommand(OnAddPhoneNumberExecute);
DeletePhoneNumberCommand = new DelegateCommand(OnDeletePhoneNumberExecute, OnDeletePhoneNumberCanExecute);
}
private void FriendPhoneNumberWrapper_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
// Ensures that the HasChanges property of the viewmodel is updated
if (!HasChanges)
{
HasChanges = _friendRepository.HasChanges();
}
// If the change in phone number causes validation errors, disable the save button
if (e.PropertyName == nameof(FriendPhoneNumberDecorator.HasErrors))
{
((DelegateCommand)SaveCommand).RaiseCanExecuteChanged();
}
}
private void OnAddPhoneNumberExecute()
{
var newNumber = new FriendPhoneNumber();
newNumber.PropertyChanged += FriendPhoneNumberWrapper_PropertyChanged;
PhoneNumbers.Add(newNumber);
Friend.PhoneNumbers.Add(newNumber);
newNumber.Number = "";
}
#endregion
}
}
When using this class, an instance of IFriendRepository is injected by a DI container and assigned to _friendRepository. The line Friend.Model.PhoneNumbers.Add in the OnAddPhoneNumberExecute method adds a PhoneNumber instance (newNumber) to Friend.PhoneNumber. This addition is tracked by Context in HasChanges method in the GenericRepository method (I checked this by debugging), and thus when the _friendRepository.HasChanges() method in FriendPhoneNumberWrapper_PropertyChanged event handler is triggered by the newNumber.Number = "" line, it returns true.
But, when I test if the HasChanges property of the class is updated when I raise the command AddPhoneNumberCommand using the following test:
namespace TrackIt.UITests.ViewModel
{
public class FriendDetailViewModelTests
{
private FriendDetailViewModel _viewModel;
private Mock<IEventAggregator> _eventAggregatorMock;
private Mock<IMessageDialogService> _messageDialogServiceMock;
private Mock<IFriendRepository> _friendRepositoryMock;
private readonly Friend testFriend = new Friend
{
Id = 2,
FirstName = "asdf",
LastName = "asdfasdf",
Email = "test#test.com",
FavoriteLanguage = new ProgrammingLanguage { Id = 1, Name = "C++"},
PhoneNumbers = new List<FriendPhoneNumber> {
new FriendPhoneNumber(){ id = 1, Number = "0123" },
new FriendPhoneNumber(){ id = 2, Number = "4567" },
}
};
public FriendDetailViewModelTests()
{
// Set up dependencies for FriendDetailViewModel class.
_messageDialogServiceMock = new Mock<IMessageDialogService>();
_eventAggregatorMock = new Mock<IEventAggregator>();
_friendRepositoryMock = new Mock<IFriendRepository>();
// Mock the friendrepository getbyidasync method
_friendRepositoryMock.Setup(dp => dp.GetByIdAsync(testFriend.Id)).ReturnsAsync(testFriend);
// Finally, create the FriendDetailViewModel!
_viewModel = new FriendDetailViewModel(_friendRepositoryMock.Object, _eventAggregatorMock.Object, _messageDialogServiceMock.Object);
}
[Fact]
public void ShouldAddNewPhoneNumberEvent()
{
// ARRANGE
// Assign testFriend to Friend (required so that the created phone number can be assigned to an instance, otherwise we get null reference exception)
PrivateObject _viewModelPrivated = new PrivateObject(_viewModel);
_viewModelPrivated.SetProperty(nameof(Friend), testFriend);
// Create a test empty phone number, to be compared later against the phone number created when user presses "Add"
var emptyPhoneNumber = new FriendPhoneNumber();
emptyPhoneNumber.Number = "";
// ACT
_viewModel.AddPhoneNumberCommand.Execute(null);
// ASSERT
// Check if the created phone number is an empty phone number, the dbContext sees there's a change,
// and that the save button can be pressed
Xunit.Assert.Contains(emptyPhoneNumber, _viewModel.PhoneNumbers,
new ByPropertyComparer<FriendPhoneNumberDecorator>(nameof(emptyPhoneNumber.Number)));
Xunit.Assert.True(_viewModel.HasChanges);
Xunit.Assert.True(_viewModel.SaveCommand.CanExecute(null));
}
}
}
the test fails at Xunit.Assert.True(_viewModel.HasChanges); line, which means the _friendRepository.HasChanges() is not updating automatically (i.e. not tracking) when the Friend.Model.PhoneNumbers.Add is called. I realize that this would be due to me mocking the IFriendRepository, and so it may not actually be tracking any changes. Am I right? Should I also mock HasChanges() of _friendRepositoryMock ? Or is there a better way to do this, maybe an in-memory database? But then it would not be a unit test?

Observer pattern implementation with complete layer separation and testability, am I doing it right?

I have doubts regarding my implementation of observer pattern, but with complete separation of concerns.
Example below is not a real life code, but just an example of idea how I want to do it.
In my solution I have two project layers:
Desktop layer (views, view models, models)
Service library layer (with observers)
My view model is a subject subscribing the observers.
Code in VM:
interface ISubject
{
void Subscribe(IObserverService observer);
void Unsubscribe(IObserverService observer);
void Notify();
}
public class MainWindowViewModel : ViewModelBase, ISubject
{
private readonly IObserverService _observer1;
private readonly IObserverService _observer2;
private ArrayList _observers;
public MainWindowViewModel(
IObserver1 observer1,
IObserver2 observer2)
{
_observer1 = observer1;
_observer2 = observer2;
ObserverCommand = new DelegateCommand(OnObserverCommand);
InitProgram();
}
private void InitProgram()
{
_observers = new ArrayList();
_observers.Add(_observer1);
_observers.Add(_observer2);
}
public List<IObserverService> Observers { get; set; }
private void OnSwitchCommand(object obj)
{
if (Jeden == true)
{
UiModel = _controlsService.SwitchOff();
}
else
{
UiModel = _controlsService.SwitchOn();
}
}
private void OnObserverCommand(object obj)
{
SomeValue++;
}
public void Subscribe(IObserverService observer)
{
Observers.Add(observer);
}
public void Unsubscribe(IObserverService observer)
{
Observers.Remove(observer);
}
public void Notify()
{
Observers.ForEach(x => x.Update(SomeValue));
}
public ICommand ObserverCommand { get; private set; }
private int _someValue;
public int SomeValue
{
get => _someValue;
set
{
_someValue = value;
InformObservers();
}
}
private void InformObservers()
{
foreach (IObserverService x in _observers)
{
x.Update(SomeValue);
}
}
}
And my observer in service layer is very simple. After Update call from the subject is displaying new MessageBox:
public interface IObserverService
{
void Update(int someValue);
}
public class Observer1 : IObserver1, IObserverService
{
public string ObserverName { get; private set; }
public Observer1(string name)
{
this.ObserverName = name;
}
public void Update(int someValue)
{
MessageBox.Show("New value: " + someValue.ToString() + " for " + ObserverName);
}
}
Observer2 is same as above.
Right now I have doubts how my constructor supposed to look like, if I want to create a new observer with a name parameter, for example: new Observer1("name1") in this case, keeping separation, should my subject's ctor look like:
public MainWindowViewModel()
{
_observerService = observerService;
IObserverService observer1 = new ObserverService("name1");
IObserverService observer2 = new ObserverService("name2");
SwitchCommnad = new DelegateCommand(OnSwitchCommand);
ObserverCommand = new DelegateCommand(OnObserverCommand);
InitProgram();
}
Is it correct approach? Is it going to be testable? Or I have to inject IObserverService somehow?
If you want to test your VM, follow IoC and don't create your ObserverServices inside it but as you say, inject IObserverService; therefore you'll be able to mock the services and test your VM without needing the whole service behavior.
I may suggest you to use Autofac or even Ninject. There are plenty of DI frameworks so look for the one that adjust to what you are looking for.
it makes sense, that MainWindowViewModel will receive some external observers via constructor:
public MainWindowViewModel(IObserver1 observer1, IObserver2 observer2)
{
_observer1 = observer1;
_observer2 = observer2;
ObserverCommand = new DelegateCommand(OnObserverCommand);
InitProgram();
}
when you create an instance of MainWindowViewModel (I assume, it will be used for MainWindowView DataContext), you will pass some real observers:
IObserverService observer1 = new ObserverService("name1");
IObserverService observer2 = new ObserverService("name2");
var vm = new MainWindowViewModel(observer1, observer2);
mainWindow.DataContext = vm;
no need for DI container here if dependencies can be resolved statically
similarly, for test you can have some TestObserverService (or IObserverService mock):
IObserverService observer1 = new TestObserverService("name1");
IObserverService observer2 = new TestObserverService("name2");
var vm = new MainWindowViewModel(observer1, observer2);
MainWindowViewModel might create some IObserverServices, if it has properties worth observing from other objects in the application (e.g. related view models)

Save information between VMs

The main idea what I am trying to do - to have one VM, which has a lot of other VMs.
The problem is to organize data transportation.
Main VM is connected with a template and other VMs have their own templates.
I use a navigator to change VMs and template selector to change templates.
Navigator:
public class NavigationController : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private List<ViewModelBase> _viewModels;
private ViewModelBase _currentViewModel;
public ViewModelBase CurrentViewModel {
get { return _currentViewModel; }
set { _currentViewModel = value; OnPropertyChanged(nameof(CurrentViewModel)); }
}
private List<ViewModelBase> _legViewModels;
private ViewModelBase _legViewModel;
public ViewModelBase LegViewModel
{
get { return _legViewModel; }
set { _legViewModel = value; OnPropertyChanged(nameof(LegViewModel)); }
}
public NavigationController()
{
_viewModels = new List<ViewModelBase>
{
new ViewModelLogin(this),
new ViewModelPhysicalOverview(this),
...list of VMs...
};
_currentViewModel = _viewModels.First();
_legViewModels = new List<ViewModelBase>
{
new SFSViewModel(this),
new BPVHipViewModel(this)
};
_legViewModel = _legViewModels.First();
}
public void NavigateTo<T>()
{
var target = _viewModels.FirstOrDefault(e => e.GetType() == typeof(T));
if (target != null)
CurrentViewModel = target;
}
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
//если PropertyChanged не нулевое - оно будет разбужено
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
My main VM:
public BPVHipViewModel LeftBPVHip { get; protected set; }
public SFSViewModel LeftSFS { get; protected set; }
public BPVHipViewModel RightBPVHip { get; protected set; }
public SFSViewModel RightSFS { get; protected set; }
public ViewModelAddPhysical(NavigationController controller) : base(controller)
{
LeftBPVHip = new BPVHipViewModel(Controller);
RightBPVHip = new BPVHipViewModel(Controller);
LeftSFS = new SFSViewModel(Controller);
RightSFS = new SFSViewModel(Controller);
Controller = controller;
base.HasNavigation = false;
ToRightBPVHipCommand = new DelegateCommand(
() =>
{
Controller.LegViewModel = RightBPVHip;
Controller.NavigateTo<LegPartViewModel>();
}
);
ToLeftBPVHipCommand = new DelegateCommand(
() =>
{
Controller.LegViewModel = LeftBPVHip;
Controller.NavigateTo<LegPartViewModel>();
}
);
ToLeftSFSCommand = new DelegateCommand(
() =>
{
Controller.LegViewModel = LeftSFS;
Controller.NavigateTo<LegPartViewModel>();
}
);
ToRightSFSCommand = new DelegateCommand(
() =>
{
Controller.LegViewModel = RightSFS;
Controller.NavigateTo<LegPartViewModel>();
}
);
}
So before I go to another VM and change my screen, I do
Controller.LegViewModel = RightSFS;
and I thought if I change something in RightSFS - it will keep changes after returning to main VM. But I guess it doesn't work like this.
In children I have:
private bool _isEmpty = true;
public bool IsEmpty {
get
{
return _isEmpty;
}
protected set {
_isEmpty = value;
OnPropertyChanged("IsEmpty");
}
}
public string ButtonText
{
get
{
if (!IsEmpty) return "Edit";
else return "Fill";
}
}
And a fn that fires before I return to parent screen:
SaveCommand = new DelegateCommand(
() =>
{
IsEmpty = false;
Controller.NavigateTo<ViewModelAddPhysical>();
}
);
so I want a button from main template to show if we already have visited child screen, in this case I want "Edit" text. But it returns "Fill" all the time, 'cause IsEmpty doesn't change from true to false for him and I don't understand how to fix it. Please help.
For me, you are trying to invent a wheel of your own. It's done, multiple times. Every MVVM framework outthere has built-in navigation.
Take a look at ReactiveUI (great framework) samples, they are doing exactly what you need.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Ninject;
using ReactiveUI.Samples.Routing.Views;
using Splat;
namespace ReactiveUI.Samples.Routing.ViewModels
{
/* COOLSTUFF: What is the AppBootstrapper?
*
* The AppBootstrapper is like a ViewModel for the WPF Application class.
* Since Application isn't very testable (just like Window / UserControl),
* we want to create a class we can test. Since our application only has
* one "screen" (i.e. a place we present Routed Views), we can also use
* this as our IScreen.
*
* An IScreen is a ViewModel that contains a Router - practically speaking,
* it usually represents a Window (or the RootFrame of a WinRT app). We
* should technically create a MainWindowViewModel to represent the IScreen,
* but there isn't much benefit to split those up unless you've got multiple
* windows.
*
* AppBootstrapper is a good place to implement a lot of the "global
* variable" type things in your application. It's also the place where
* you should configure your IoC container. And finally, it's the place
* which decides which View to Navigate to when the application starts.
*/
public class AppBootstrapper : ReactiveObject, IScreen
{
public RoutingState Router { get; private set; }
public AppBootstrapper(IMutableDependencyResolver dependencyResolver = null, RoutingState testRouter = null)
{
Router = testRouter ?? new RoutingState();
dependencyResolver = dependencyResolver ?? Locator.CurrentMutable;
// Bind
RegisterParts(dependencyResolver);
// TODO: This is a good place to set up any other app
// startup tasks, like setting the logging level
LogHost.Default.Level = LogLevel.Debug;
// Navigate to the opening page of the application
// you can set any property of this new VM to transport data
Router.Navigate.Execute(new WelcomeViewModel(this));
}
private void RegisterParts(IMutableDependencyResolver dependencyResolver)
{
dependencyResolver.RegisterConstant(this, typeof(IScreen));
dependencyResolver.Register(() => new WelcomeView(), typeof(IViewFor<WelcomeViewModel>));
}
}
}
I used MessageBus pattern, it was perfect solution for me
class Subscription
{
public object Instance { get; set; }
public Action<object, object> Handler;
}
public class MessageBus
{
#region Singleton
private static readonly MessageBus _instance = new MessageBus();
public static MessageBus Default => _instance;
private MessageBus()
{
}
#endregion
private readonly Dictionary<string, List<Action<object, object>>> _hadlersMap
= new Dictionary<string, List<Action<object, object>>>();
public void Call(string name, object sender, object data)
{
List<Action<object, object>> handlers;
if(!_hadlersMap.TryGetValue(name.ToUpper(), out handlers))
return;
foreach (var handler in handlers)
{
handler?.Invoke(sender,data);
}
}
public void Subscribe(string name, Action<object, object> handler)
{
name = name.ToUpper();
List<Action<object, object>> handlers;
if (!_hadlersMap.TryGetValue(name, out handlers))
{
handlers = new List<Action<object, object>>{ handler };
_hadlersMap.Add(name, handlers);
}
else
{
handlers.Add(handler);
}
}
}
So I have never seem things be done this way. Have you looked at Windsor. I believe dependency injection and inversion of control could improve the scalability here. As far as suggestions go.
There is a lot of instantiation going on in many different places in the code here. Maybe creating a factory to handle all the new-ing up. IOC would help with that as well. You could place your list of models globally. App.Current.Properties[ "someVm" ] = vmInstance; if you are wanting to save the vm state.
Another way to persist the vm state would of course be to make that vm a singleton ensuring that when called it returns that only instance if already exists or instantiates if not.
Finally, I have persisted vm state upon unloading and reading state from somewhere upon loading. This is common and many default controls do this.

MVVMCROSS - Pass a parameter to a ViewModel

I am trying to pass a parameter to a child ViewModel constructor which throws "MvvmCross.Platform.Exceptions.MvxException: Failed to construct and initialize ViewModel ... MvxIoCResolveException: Failed to resolve parameter for parameter myParam of type MyType..."
MyChildViewModel.cs
public class MyChildViewModel : MvxViewModel
{
private MyType _myParam;
public MyType MyParam
{
get { return _myParam; }
set
{
if (SetProperty(ref _myParam, value))
{
RaisePropertyChanged(() => MyParam);
}
}
}
public MyChildViewModel(MyType myParam)
{
_myParam = myParam;
}
}
In my parent ViewModel I have:
public ICommand ShowDialogCommand { get; private set; }
ShowDialogCommand = new MvxCommand<MyType>(e => ShowViewModel<MyChildViewModel>(e));
Parent activity call:
ViewModel.ShowDialogCommand.Execute(VarOfMyType);
I am obviously doing something wrong. Is this even remotely acceptable approach to pass data to a child ViewModel? What's the best practice?
Thank you in advance for your valuable time.
If you read up on the documentation
it is easy to pass object with the MvxNavigationService: https://www.mvvmcross.com/documentation/fundamentals/navigation
Note that the documentation is for MvvmCross 5.2 which is currently in a nightly release, but almost the same works for 5.0 and onwards.
In your ViewModel this could look like:
public class MyViewModel : MvxViewModel
{
private readonly IMvxNavigationService _navigationService;
public MyViewModel(IMvxNavigationService navigationService)
{
_navigationService = navigationService;
}
public override void Prepare()
{
//Do anything before navigating to the view
}
public async Task SomeMethod()
{
await _navigationService.Navigate<NextViewModel, MyObject>(new MyObject());
}
}
public class NextViewModel : MvxViewModel<MyObject>
{
public override void Prepare(MyObject parameter)
{
//Do anything before navigating to the view
//Save the parameter to a property if you want to use it later
}
public override async Task Initialize()
{
//Do heavy work and data loading here
}
}
From this website the way they did it is (adapted and modified for your case):
public ICommand ShowDialogCommand { get; private set; }
ShowDialogCommand = new MvxCommand<MyType>(ShowMyVM);
private void ShowMyVM(MyType e)
{
if (e != null)
ShowViewModel<SingleClientViewModel>(e);
else
{
//handle case where your parameter is null
}
}

Create Dictionary for delegates and events

I want to create a dictionary like this:
public class MyClass
{
public delegate void CreateClickEvent(string value);
public event CreateClickEvent OnCreateClick;
public delegate void CreateHoverEvent(string value);
public event CreateHoverEvent OnCreateHover;
private Dictionary<string,Delegate> _myDictionary = null;
public MyClass()
{
_myDictionary = new Dictionary<string,Delegate>();
_myDictionary.Add("Click", OnCreateClick);
_myDictionary.Add("Hover", OnCreateHover);
}
public void Call(string name)
{
Delegate action;
if (_myDictionary.TryGetValue(name, out action))
{
if (action != null)
action.DynamicInvoke( "something" );
else
Console.WriteLine("null");
}
else
Console.WriteLine("Couldn't find action");
}
}
public class Tester()
{
public Tester()
{
MyClass MyClass = new MyClass();
myClass.OnCreateClick += RaiseCreateClickEvent;
myClass.OnCreateHover += RaiseCreateHoverEvent;
}
public void RaiseCreateClickEvent(string value)
{
///do it here
}
public void RaiseCreateHoverEvent(string value)
{
///do it here
}
}
but unfortunately, the method RaiseCreateClickEvent or RaiseCreateHoverEvent (class Tester) is not calling from Call method (class MyClass). And it's not giving any error and action variable is looking null and printing null.
What am I doing wrong here?
You have dictionary of delegates, but it keeps values of your events at the moment of creation of the dictionary. At that point both OnCreateXXXX are null.
As an option can use dictionary of Action and call event in each so action will use current value of the event, not the initial one:
private Dictionary<string,Action<string>> _myDictionary =
new Dictionary<string,Action<string>>();
_myDictionary.Add("Click", s => OnCreateClick(s));
_myDictionary.Add("Hover", s => OnCreateHover(s));
And use:
_myDictionary["Click"] ("sometext");
Note good reading on delegates (and especially how + / += impacts value) is available http://csharpindepth.com/Articles/Chapter2/Events.aspx

Categories