I need to request values using functions in a DLL provided by the manufacturer of my particular piece of hardware (a weather station). I'm new to C#, and the concepts of delegates/events are tough to wrap my head around. Nonetheless, I've managed to pull the functions from the DLL and verify that data makes it through. My issue is with polling the instrument periodically with a Timer. In Initialize(), an object is instantiated, but the event isn't handled leaving the object null. I'm out of ideas, and would like some advice!
public class HardwareData : EventArgs
{
public float OutsideTemp { get; set; }
public int OutsideHum { get; set; }
public float WindSpeed { get; set; }
public int WindDirection { get; set; }
}
public class Hardware : IDisposable
{
private static Hardware v;
private System.Timers.Timer hardwareTimer;
private int counter = 0;
private static readonly object padlock = new object();
public static Hardware Instance
{
get
{
lock (padlock)
{
if (v == null)
v = new Hardware();
return v;
}
}
}
public void Initialize()
{
try
{
hardwareTimer = new System.Timers.Timer(500);
hardwareTimer.Elapsed += new ElapsedEventHandler(hardwareTimer_Elapsed);
HardwareVue.OpenCommPort_V(3, 19200); //COM port and baud rate are verified.
hardwareTimer.Start();
}
catch (Exception ex)
{
throw new InvalidOperationException("Unable to initialize.", ex);
}
}
public HardwareData LastHardware { get; set; }
void hardwareTimer_Elapsed(object sender, ElapsedEventArgs e)
{
try
{
counter += 1;
Console.WriteLine(counter);
HardwareVue.LoadCurrentHardwareData_V();
HardwareData v = new HardwareData()
{
OutsideTemp = HardwareVue.GetOutsideTemp_V(),
OutsideHum = HardwareVue.GetOutsideHumidity_V(),
WindSpeed = HardwareVue.GetWindSpeed_V(),
WindDirection = HardwareVue.GetWindDir_V()
};
LastHardware = v;
}
catch (Exception) { }
}
public void Dispose()
{
HardwareVue.CloseCommPort_V();
hardwareTimer.Stop();
}
}
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
Hardware test = new Hardware();
try
{
if (test != null)
{
test.Initialize();
test.Dispose();
Assert.AreEqual(0, test.LastHardware.OutsideHum);
}
}
catch (NullReferenceException ex)
{
Console.WriteLine("Object is null.");
}
// Console.WriteLine(test.LastHardware.OutsideHum);
}
}
When working with timers, you need to enable the timer and make sure the events are rasied:
hardwareTimer.Enabled = true;
hardwareTimer.CanRaiseEvents = true;
For Reference: Timers on MSDN
Edit
In addition to the other comments to both the OP's question and this answer, the issue with the LastHardware being null is due to the property never being instantiated before the timer initially fires. To resolve this, you should instantiate the LastHardware property in the default constructor (or in the Initialize method):
public Hardware()
{
LastHardware = new HardwareData();
}
Of course, you'd want to set some default values upon instantiation.
Related
I am currently developing a multiplayer web game with timer using ASP.NET Core. For real time communication I am using SignalR. Everythning up to the moment had been working just fine.
The way I implemented the timer functionality was the following: I created a static dictionary where the key is the Id of the game and the value is the timer corresponding to the given game. Then I exposed public methods for managing the state of the dictionary:
public class TimerManager
{
private static readonly Dictionary<string, GameTimer> gameTimersByGameIds = new();
public void AttachTimerToGameState(GameTimer timer, string gameId)
{
if (!gameTimersByGameIds.ContainsKey(gameId))
{
gameTimersByGameIds.Add(gameId, timer);
return;
}
gameTimersByGameIds[gameId] = timer;
}
public GameTimer? GetTimer(string gameId)
{
if (!gameTimersByGameIds.ContainsKey(gameId))
{
return null;
}
return gameTimersByGameIds[gameId];
}
public GameTimer CreateTimer(GameState gameState)
{
if (gameState.RemainingSecondsByUserNames.Count == 0)
{
return ActivatorUtilities.CreateInstance<StandardTimer>(_serviceProvider, gameState);
}
else
{
return ActivatorUtilities.CreateInstance<ChessTimer>(_serviceProvider, gameState, _gamesService);
}
}
}
I created my own base GameTimer class which encapsulates a System.Timers.Timer inside of it:
public abstract class GameTimer
{
protected readonly System.Timers.Timer _timer = new();
public virtual void Reset()
{
_timer.AutoReset = true;
_timer.Interval = 1_000;
}
public virtual void Start()
{
_timer.Start();
}
public virtual void Dispose()
{
_timer.Dispose();
}
}
Then I inherited from the abstract GameTimer class to create different types of timers.
public class StandardTimer : GameTimer
public class ChessTimer : GameTimer
The problem is inside of the ChessTimer class:
public class ChessTimer : GameTimer
{
private readonly GameState _gameState;
private readonly IGameService _gameService;
private readonly IHubContext<GameHub, IGameClient> _hubContext;
private readonly IMatchmakingService _matchmakingService;
private readonly IGamesService _gamesService;
public ChessTimer(
GameState gameState,
IGamesService gamesService,
IGameService gameService,
IHubContext<GameHub, IGameClient> hubContext,
IMatchmakingService matchmakingService)
{
_gameState = gameState;
_gameService = gameService;
_hubContext = hubContext;
_matchmakingService = matchmakingService;
_gamesService = gamesService;
Reset();
_timer.Elapsed += async (sender, args) => await OnTimedEvent(sender, args);
}
public int SecondsRemaining { get; private set; }
public override void Reset()
{
string currentPlayerName = _gameState.CurrentTeam.CurrentPlayer.UserName;
SecondsRemaining = _gameState.RemainingSecondsByUserNames[currentPlayerName];
base.Reset();
}
private async Task OnTimedEvent(object? sender, ElapsedEventArgs args)
{
if (SecondsRemaining >= 0)
{
string currentPlayerUserName = _gameState.CurrentTeam.CurrentPlayer.UserName;
_gameState.RemainingSecondsByUserNames[currentPlayerUserName] = SecondsRemaining;
int minutes = SecondsRemaining / 60;
int seconds = SecondsRemaining % 60;
var viewModel = new UpdateGameTimerViewModel
{
Minutes = minutes,
Seconds = seconds,
};
foreach (Player player in _gameState.Players)
{
if (player.ConnectionId == null)
{
continue;
}
await _hubContext.Clients
.Client(player.ConnectionId)
.UpdateGameTimer(viewModel);
}
SecondsRemaining--;
return;
}
if (_gameState.RemainingSecondsByUserNames.All(x => x.Value <= 0))
{
_gameState.EndGame();
}
else
{
_gameState.NextTeam();
}
foreach (Player player in _gameState.Players)
{
_gameService.FillPlayerTiles(_gameState, player);
}
foreach (Player player in _gameState.Players)
{
if (player.ConnectionId == null)
{
continue;
}
var viewModel = _gameService.MapFromGameState(_gameState, player.UserName);
await _hubContext.Clients
.Client(player.ConnectionId)
.UpdateGameState(viewModel);
if (_gameState.IsGameOver)
{
_matchmakingService.RemoveUserFromGame(player.UserName);
await _hubContext.Groups
.RemoveFromGroupAsync(
player.ConnectionId, _gameState.GameId);
}
}
if (_gameState.IsGameOver)
{
_matchmakingService.RemoveGameState(_gameState.GameId);
await _gamesService!.SaveGameAsync(new SaveGameInputModel
{
GameId = _gameState.GameId,
Players = _gameState.Players
});
Dispose();
}
Reset();
}
}
Basically the way my OnTimedEvent method works is that it executes every second. Then checks whether the time of all players has run out. If it hasn't it updates the state of the game, otherwise is ends the game. After everything else is done, I try to save the game inside my database in order to enable the users to see summary about their played games. I am using a class called GamesService for this task. It uses UserManager internally to perform some work with the Users in the database.
if (_gameState.IsGameOver)
{
_matchmakingService.RemoveGameState(_gameState.GameId);
await _gamesService!.SaveGameAsync(new SaveGameInputModel
{
GameId = _gameState.GameId,
Players = _gameState.Players
});
Dispose();
}
Reset();
The problem is that when the game ends and the above code is executed it produces the following exception:
Unhandled exception. Unhandled exception. System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'UserManager`1'.
at Microsoft.AspNetCore.Identity.UserManager`1.ThrowIfDisposed()
at Microsoft.AspNetCore.Identity.UserManager`1.FindByNameAsync(String userName)
at SuperScrabble.Services.Data.Users.UsersService.GetByUserNameAsync(String userName) in C:\Users\georg\Source\Github\SuperScrabble\src\Server\Services\SuperScrabble.Services.Data\Users\UsersService.cs:line 64
at SuperScrabble.Services.Data.Games.GamesService.SaveGameAsync(SaveGameInputModel input) in C:\Users\georg\Source\Github\SuperScrabble\src\Server\Services\SuperScrabble.Services.Data\Games\GamesService.cs:line 52
at SuperScrabble.WebApi.Timers.ChessTimer.OnTimedEvent(Object sender, ElapsedEventArgs args) in C:\Users\georg\Source\Github\SuperScrabble\src\Server\WebApi\SuperScrabble.WebApi\Timers\ChessTimer.cs:line 120
at SuperScrabble.WebApi.Timers.ChessTimer.<.ctor>b__5_0(Object sender, ElapsedEventArgs args) in C:\Users\georg\Source\Github\SuperScrabble\src\Server\WebApi\SuperScrabble.WebApi\Timers\ChessTimer.cs:line 35
at System.Threading.Tasks.Task.<>c.<ThrowAsync>b__128_1(Object state)
at System.Threading.QueueUserWorkItemCallback.<>c.<.cctor>b__6_0(QueueUserWorkItemCallback quwi)
at System.Threading.ExecutionContext.RunForThreadPoolUnsafe[TState](ExecutionContext executionContext, Action`1 callback, TState& state)
at System.Threading.QueueUserWorkItemCallback.Execute()
at System.Threading.ThreadPoolWorkQueue.Dispatch()
at System.Threading.PortableThreadPool.WorkerThread.WorkerThreadStart()
at System.Threading.Thread.StartCallback()
It seems to me that the _gamesService object which I pass to the CreateInstance() method inside the TimerManager.CreateTimer() method is already disposed or that some of the services it uses internally are.
public GameTimer CreateTimer(GameState gameState)
{
if (gameState.RemainingSecondsByUserNames.Count == 0)
{
return ActivatorUtilities.CreateInstance<StandardTimer>(_serviceProvider, gameState);
}
else
{
return ActivatorUtilities.CreateInstance<ChessTimer>(_serviceProvider, gameState, _gamesService);
}
}
I am not sure if what I am trying to do is correct. I need to use service classes inside my timer classes to perform operations every time the timer ticks. However, I cannot use dependency injection and that's the reason why I use the ActivatorUtilities class to instantiate the object.
I create all timers from a method inside my SignalR hub class:
private async Task StartGameAsync()
{
var gameState = _matchmakingService.GetGameState(UserName);
string gameId = gameState.GameId;
foreach (Player player in gameState.Players)
{
await Groups.AddToGroupAsync(player.ConnectionId!, gameId);
}
var timer = _timerManager.CreateTimer(gameState);
Console.WriteLine(timer.GetType().Name);
_timerManager.AttachTimerToGameState(timer, gameId);
await Clients.Group(gameId).StartGame(gameId);
await UpdateGameStateAsync(gameState);
timer.Start();
}
So my question would be: Is it possible to find a work around of the problem?
If you need any more clarification please feel free to ask me anything. Any help would be greatly appreciated! Thanks
I want to test whether my event has to be raised or not using unit testing. In my project, I passed a progress value from one class to another using event handler. I working on MVVM method so, I passed the value from Model class to ViewModel class. How to write unit test for the event handler.
I try to write unit test for the event handlers called GetTotalFileSize and TransferredFileSize.But i could not raise the events on test. So, What I have to do?
[Test]
public void IsGetTotalFileSizeEventFired()
{
worflowManager = new Mock<IWorkflowManager>().Object;
ripWatcherWindowShellViewModel =
new RipWatcherWindowShellViewModel(worflowManager);
ripWatcherWindowShellViewModel.GetTotalFileSize
+= delegate { eventRaised = true; };
Assert.IsTrue(eventRaised);
}
//production code is..
public RipWatcherWindowShellViewModel(IWorkflowManager workflowManager)
{
WorkflowManager = workflowManager;
workflowManager.TransferredUsfFileSizeChanged
+= workflowManager_TransferredUsfFileSizeChanged;
workflowManager.GetTotalUsfFileSize
+= workflowManager_GetTotalFileSize;
}
public void workflowManager_GetTotalFileSize(object sender, FileSizeChangedEventArgs e)
{
if(e.FileSize== 0)
{
throw new ArgumentException("We cannot calculate progress percentage because total file size is 0");
}
TotalUsfFileSize = e.FileSize;
}
public void workflowManager_TransferredUsfFileSizeChanged(object sender, FileSizeChangedEventArgs e)
{
if(e.FileSize !=0)
{
TransferredUsfFileSize = e.FileSize;
}
else
{
tempFileSize += TransferredUsfFileSize;
}
/*Calculating progress percentage for updating progress bar*/
ProgressPercentage = (int)(((TotalUsfFileSize -(TotalUsfFileSize-(tempFileSize+TransferredUsfFileSize)))/TotalUsfFileSize)* 100);
}
public Double TransferredUsfFileSize
{
get;
set;
}
/// <summary>Gets or sets the total file size</summary>
public Double TotalUsfFileSize
{
get;
set;
}
public IWorkflowManager WorkflowManager { get; set; }
public int ProgressPercentage
{
get
{
return percentage;
}
set
{
percentage = value;
OnPropertyChanged("ProgressPercentage");
}
}
The value came from..
public void CopyEx(string sourceFilePath,string destinationFilePath)
{
try
{
lock (locker)
{
CopyFileEx(sourceFilePath, destinationFilePath,
new CopyProgressRoutine(this.CopyProgressHandler),
IntPtr.Zero, ref pbCancel,
CopyFileFlags.COPY_FILE_RESTARTABLE);
}
}
catch(Exception ex)
{
throw new Exception(ex.message);
}
}
private CopyProgressResult CopyProgressHandler(long total,
long transferredFileSize, long streamSize,
long StreamByteTrans, uint dwStreamNumber,
CopyProgressCallbackReason reason,
IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData)
{
TransferredUsfFileSizeChanged.SafeInvoke(this,
new FileSizeChangedEventArgs(transferredFileSize));
return CopyProgressResult.PROGRESS_CONTINUE;
}
public EventHandler<FileSizeChangedEventArgs>
TransferredUsfFileSizeChanged;
I trying to allow people to write to NFC tags using my app, so that my app gets launched with a custom parameter. I want to be able to reprogram NFC tags which already have data on them.
I am using the following code but the problem is, that WP always recognizes the action which is already on the NFC tag and interrupts because it wants to launch the NFC tag action which was written anytime before.
How can I tell the OS to stop triggering the action of the tag so that I can immediately rewrite it?
public enum NfcHelperState
{
Initializing,
Waiting,
Ready,
Writing,
Finished,
Error,
NoDeviceFound
}
public class NfcHelper
{
private NfcHelperState _state = NfcHelperState.Initializing;
public NfcHelperState State
{
get { return _state; }
}
private ProximityDevice _nfcDevice;
private long _subscriptionId;
public NfcHelper()
{
Init();
}
public void Init()
{
UpdateState();
_nfcDevice = ProximityDevice.GetDefault();
if (_nfcDevice == null)
{
UpdateState(NfcHelperState.NoDeviceFound);
return;
}
UpdateState(NfcHelperState.Waiting);
}
private void UpdateState(NfcHelperState? state = null)
{
if (state.HasValue)
{
_state = state.Value;
}
if (OnStatusMessageChanged != null)
{
OnStatusMessageChanged(this, _state);
}
}
public void WriteToTag()
{
UpdateState(NfcHelperState.Ready);
_subscriptionId = _nfcDevice.SubscribeForMessage("WriteableTag", WriteableTagDetected);
}
private void WriteableTagDetected(ProximityDevice sender, ProximityMessage message)
{
UpdateState(NfcHelperState.Writing);
try
{
var str = "action=my_custom_action";
str += "\tWindowsPhone\t";
str += CurrentApp.AppId;
_nfcDevice.PublishBinaryMessage("LaunchApp:WriteTag", GetBufferFromString(str),
WriteToTagComplete);
}
catch (Exception e)
{
UpdateState(NfcHelperState.Error);
StopWaitingForTag();
}
}
private void WriteToTagComplete(ProximityDevice sender, long messageId)
{
sender.StopPublishingMessage(messageId);
UpdateState(NfcHelperState.Finished);
StopWaitingForTag();
}
private void StopWaitingForTag()
{
_nfcDevice.StopSubscribingForMessage(_subscriptionId);
}
private static IBuffer GetBufferFromString(string str)
{
using (var dw = new DataWriter())
{
dw.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf16LE;
dw.WriteString(str);
return dw.DetachBuffer();
}
}
public delegate void NfcStatusMessageChangedHandler(object myObject, NfcHelperState newState);
public event NfcStatusMessageChangedHandler OnStatusMessageChanged;
}
WriteToTag is called when a button in my app is tapped and the app waits for a writable tag. If a writable tag is recognized, WriteableTagDetected gets called and immediately starts the writing process. However, this is interrupted by the WP dialog which asks whether to perform the NFC action or not. After writing, WriteToTagComplete should be called, where StopWaitingForTag gets called and ends the write process.
I hope you guys can help me :)
Turns out I thought the wrong way. I didn't need to wait for a tag to arrive in order to rewrite it. In fact, there's no need to do _nfcDevice.SubscribeForMessage("WriteableTag", WriteableTagDetected); before writing. Just start using PublishBinaryMessage and it will write to the tag once it arrives at the device.
My final code looks like the following:
public enum NfcHelperState
{
Initializing,
Ready,
WaitingForWriting,
FinishedWriting,
ErrorWriting,
NoDeviceFound
}
public class NfcHelper
{
private NfcHelperState _state = NfcHelperState.Initializing;
public NfcHelperState State
{
get { return _state; }
}
private ProximityDevice _nfcDevice;
private long? _writingMessageId;
public NfcHelper()
{
Init();
}
public void Init()
{
UpdateState();
_nfcDevice = ProximityDevice.GetDefault();
if (_nfcDevice == null)
{
UpdateState(NfcHelperState.NoDeviceFound);
return;
}
UpdateState(NfcHelperState.Ready);
}
private void UpdateState(NfcHelperState? state = null)
{
if (state.HasValue)
{
_state = state.Value;
}
if (OnStatusMessageChanged != null)
{
OnStatusMessageChanged(this, _state);
}
}
public void WriteToTag()
{
StopWritingMessage();
UpdateState(NfcHelperState.WaitingForWriting);
try
{
var str = new StringBuilder();
str.Append("action=my_custom_action");
str.Append("\tWindowsPhone\t{");
str.Append(CurrentApp.AppId);
str.Append("}");
_writingMessageId = _nfcDevice.PublishBinaryMessage("LaunchApp:WriteTag", GetBufferFromString(str.ToString()),
WriteToTagComplete);
}
catch
{
UpdateState(NfcHelperState.ErrorWriting);
StopWritingMessage();
}
}
private void WriteToTagComplete(ProximityDevice sender, long messageId)
{
UpdateState(NfcHelperState.FinishedWriting);
StopWritingMessage();
}
private void StopWritingMessage()
{
if (_writingMessageId.HasValue)
{
_nfcDevice.StopPublishingMessage(_writingMessageId.Value);
_writingMessageId = null;
}
}
private static IBuffer GetBufferFromString(string str)
{
using (var dw = new DataWriter())
{
dw.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf16LE;
dw.WriteString(str);
return dw.DetachBuffer();
}
}
public delegate void NfcStatusMessageChangedHandler(object myObject, NfcHelperState newState);
public event NfcStatusMessageChangedHandler OnStatusMessageChanged;
}
I have an issue with data seemingly being reset to its default values.
The class is as follows (objectIDs is a simple enumeration):
public class Output_args: EventArgs {
public objectIDs outputtype;
public int internalID;
public int verdict;
public int outputID;
public long entrytime;
public Output_args Copy() {
Output_args args = new Output_args();
args.entrytime = this.entrytime;
args.internalID = this.internalID;
args.outputID = this.outputID;
args.outputtype = this.outputtype;
args.verdict = this.verdict;
return args;
}
}
The following code creates the object. It runs in a specific thread, let's say Thread1.
class Class1 {
EventWaitHandle ewh = new EventWaitHandle(false, EventResetMode.AutoReset);
public event EventHandler<Output_args> newOutput;
public void readInput(){
List<Output_args> newoutputlist = new List<Output_args>();
/*
* code to determine the outputs
*/
Output_args args = new Output_args();
args.outputtype = objectIDs.stepID;
args.internalID = step[s].ID;
args.verdict = verdict;
args.entrytime = System.DateTime.Now.Ticks;
newoutputlist.Add(args.Copy());
if (newOutput != null && newoutputlist.Count > 0) {
// several outputs are being sent sequentially but for simplicity i've removed the for-loop and decision tree
try {
newOutput(null, newoutputlist[0].Copy());
} catch (Exception) { }
}
}
}
1 of the subscribers to this event has the following code. The processor method runs on a thread of a camerafeed. The newOutput event handler is being run on Thread1.
class Class2: Form {
private Output_args lastoutput = new Output_args();
public void newOutput(object sender, Output_args args) {
lock (lastoutput) {
lastoutput = args.Copy();
}
}
public void processor(){
lock (lastoutput) {
if (lastoutput.entrytime + 10000000 > System.DateTime.Now.Ticks) {
// do something
}
}
}
}
When the eventhandler 'newOutput' of Class2 is being called, the debugger shows that the copy works as expected and 'entrytime' is given the expected number of ticks.
However, when the processor method wants to read the 'entrytime', its value is 0. All other fields also have their default value assigned.
I've tried replacing the object 'lastoutput' with a simple field of the type long and removed the locks but the results are the same: it gets assigned properly in 'newOutput' but has its default value (0) in the processor method.
Any ideas on why this is happening?
you should not lock on the object lastoutput, but on another object, because you reassign the field.
The processor start and lock on the default field instance new Output_args() initialized with default values
class Class2: Form {
private object mylock = new object();
private Output_args lastoutput;
public void newOutput(object sender, Output_args args) {
lock (mylock) {
lastoutput = args.Copy();
}
}
public void processor(){
lock (mylock) {
if (lastoutput == null) {
//nothing to consume yet
}
else if (lastoutput.entrytime + 10000000 > System.DateTime.Now.Ticks) {
// do something
}
}
}
}
but this discard lastouput if consumer is slower than producer. You can use a queue ( or another collection ) as buffer if needed.
class Class2 {
private Queue<Output_args> outputs = new Queue<Output_args>();
public void newOutput(object sender, Output_args args) {
lock (outputs) {
outputs.Enqueue(args.Copy());
}
}
public void processor(){
lock (outputs) {
if (outputs.Count > 0) {
var lastoutput = outputs.Dequeue();
if (lastoutput.entrytime + 10000000 > System.DateTime.Now.Ticks) {
// do something
}
}
}
}
}
demo: https://dotnetfiddle.net/daHVD1
I'm unsure as to what is the best approach for passing events down the line to parent classes and in need of some feedback.
The example code below tries to illustrate what I want to achieve.
namespace test {
public delegate void TestCompletedEventHandler(object sender,
TestCompletedEventArgs e);
public class Manager {
CarList m_carlist = null;
public CarList Cars {
get { return m_carlist; }
set { m_carlist = value; }
}
public Manager() {
Cars = new CarList(this);
}
public void Report(bool successfull) {
//...
}
}
public class CarList : List<Car> {
protected internal event TestCompletedEventHandler
Car_TestCompleted = null;
protected readonly Manager m_manager = null;
public Manager Manager {
get { return m_manager; }
}
public CarList(Manager manager) {
m_manager = manager;
}
public void Test() {
foreach(Car car in this) {
bool ret = car.Test();
manager.Report(ret);
}
}
public void Add(Car car) {
//Is this a good approach?
car.TestCompleted +=
new TestCompletedEventHandler(Car_TestCompleted_Method);
base.Add(car);
}
private void Car_TestCompleted_Method(object sender,
TestCompletedEventArgs e)
{
if(Car_TestCompleted != null) Car_TestCompleted(sender, e);
}
}
public class Car {
protected internal event TestCompletedEventHandler
TestCompleted = null;
public bool Test() {
//...
if(TestCompleted != null) TestCompleted(this,
new TestCompletedEventArgs())
}
}
public class TestCompletedEventArgs : EventArgs {
//...
}
}
using test;
Manager manager = new Manager();
manager.Cars.Car_TestCompleted +=
new TestCompletedEventHandler (Car_TestCompleted_Method);
manager.Cars.Test();
Another more specific example:
//Contains DataItems and interfaces for working with them
class DataList
{
public List<DataItem> m_dataitems { get; set; }
public TestManager m_testmanager { get; set; }
// ...
}
class DataItem
{
// ...
}
//A manager class for running tests on a DataList
class TestManager
{
public List<TestSource> m_sources { get; set; }
public WorkerManager m_workermanager { get; set; }
// ...
}
//A common interface for Tests
abstract class TestSource
{
public event EventHandler<EventArgs<object>> Completed = null;
protected TestManager m_owner { get; set; }
public abstract void RunAsync();
// ...
}
//A test
class Test1 : TestSource
{
public virtual void RunAsync()
{
//Add commands
//Run workers
//Report progress to DataList and other listeners (like UI)
//Events seem like a bad approach since they need to be forwarded through many levels of abstraction
if(Completed != null) Completed(this, new EventArgs<object>(null));
}
// ...
}
//Manages a number of workers and a queue of commands
class WorkerManager
{
public List<MyWorker> m_workers { get; set; }
public Queue<Command> m_commands { get; set; }
}
//Wrapper for BackgroundWorker
class MyWorker
{
// ...
}
//Async command
interface Command
{
// ...
}
I think you may have just over implemented this a bit... It looks like you are trying to use async operations. Even if you are using sync operations though, typically you'd just use callback methods instead of events in a case like this...
Here is an example of things to change to use callbacks here:
//new delegate
public delegate void CarReportCallback(Car theCar, bool result);
//in the Manager class, make report conform to delegate's signature
public void Report(Car theCar, bool result)
{
//do something, you know which car and what the result is.
}
//in the CarList class pass a reference to the report method in
public void Test()
{
foreach(Car car in this)
{
car.Test(manager.Report);
}
}
//in the Car class use the delegate passed to invoke the reporting
public void Test(CarReportCallback callback)
{
//... do stuff
callback(this, isTestCompleted);
}
It seems reasonable, but I'm not really sure what the use case is and how this would be used.
You've got a strong concept of containment going on, but I'm not really sure why. Also, it's kind of weird that the CarList 'sort of' seems to have ownership of the individual cars.
Additionally, I don't know why Test() on the Car class would both return a result and raise an event. It seems like you're having two different paths to return the same data. And the Manager class seems completely redundant with the CarList class at first glance.
What is the problem you're actually trying to solve here? That might help me with defining a good solution to it.
It wouldn't make sense to just have each car call an event which calls an event on the parent list. I would do it more like this:
namespace test {
public delegate void TestCompletedEventHandler(object sender,
TestCompletedEventArgs e);
public class Manager {
CarList m_carlist = null;
public CarList Cars {
get { return m_carlist; }
set { m_carlist = value; }
}
public Manager() {
Cars = new CarList(this);
}
public void Report(bool successful) {
//...
}
}
public class CarList : List<Car> {
protected readonly Manager m_manager = null;
protected List<Action<object, TestCompletedEventArgs>> delegatesList = new List<Action<object, TestCompletedEventArgs>>();
public Manager Manager {
get { return m_manager; }
}
public CarList(Manager manager) {
m_manager = manager;
}
public void Test() {
foreach(Car car in this) {
bool ret = car.Test();
manager.Report(ret);
}
}
public void Add(TestCompletedEventHandler e) {
foreach (Car car in this) {
car.OnTestCompleted += e;
}
delegatesList.Add(e);
}
public void Add(Car car) {
foreach(Action a in delegatesList)
{
car.OnTestCompleted += a;
}
base.Add(car);
}
}
public class Car {
protected internal event TestCompletedEventHandler OnTestCompleted = null;
public bool Test() {
//...
if (OnTestCompleted != null) OnTestCompleted(this, new TestCompletedEventArgs());
}
}
public class TestCompletedEventArgs : EventArgs {
//...
}
}
using test;
Manager manager = new Manager();
Manager.Cars.Add(new Car());
manager.Cars.Add(new Car());
manager.Cars.Add(new Car());
manager.Cars.Add((sender, args) =>
{
//do whatever...
})
manager.Cars.Test();
manager.Cars.Add(new Car());