Best way to share data between two child components in Blazor - c#

I have this code.
<ParentComponent>
<ChildComponet>
#renderFragment
</ChildComponent>
<ChildComponetn>
<GridComponent Data="#dataList"/>
</ChildComponent>
</ParentComponent>
where #renderFragment is dynamically render componet and Grid componet is list of some data with actions like "add new", "edit record", "delete".
If we click "add new", form for add new record is opened dynamically in #renderFragment and we want to refresh grid data after submit form but we don't know how to share some data between two child components. Same is about edit form, when some record is edited, we need to refresh grid component to show edited data.
If need more code and data about it please comment.

You may define a class service that implements the State pattern and the Notifier pattern to handle the state of your objects, pass state to objects, and notify subscriber objects of changes.
Here's a simplified example of such service, which enables a parent component to communicate with his children.
NotifierService.cs
public class NotifierService
{
private readonly List<string> values = new List<string>();
public IReadOnlyList<string> ValuesList => values;
public NotifierService()
{
}
public async Task AddTolist(string value)
{
values.Add(value);
await Notify?.Invoke();
}
public event Func<Task> Notify;
}
Child1.razor
#inject NotifierService Notifier
#implements IDisposable
<div>User puts in something</div>
<input type="text" #bind="#value" />
<button #onclick="#AddValue">Add value</button>
#foreach (var value in Notifier.ValuesList)
{
<p>#value</p>
}
#code {
private string value { get; set; }
public async Task AddValue()
{
await Notifier.AddTolist(value);
}
public async Task OnNotify()
{
await InvokeAsync(() =>
{
StateHasChanged();
});
}
protected override void OnInitialized()
{
Notifier.Notify += OnNotify;
}
public void Dispose()
{
Notifier.Notify -= OnNotify;
}
}
Child2.razor
#inject NotifierService Notifier
<div>Displays Value from service and lets user put in new value</div>
<input type="text" #bind="#value" />
<button #onclick="#AddValue">Set Value</button>
#code {
private string value { get; set; }
public async Task AddValue()
{
await Notifier.AddTolist(value);
}
}
Usage
#page "/"
<p>
<Child1></Child1>
</p>
<p></p>
<p>
<Child2></Child2>
</p>
Startup.ConfigureServices
services.AddScoped<NotifierService>();
Hope this helps...

There are a few ways to do it, I just learned a really cool way using a Singleton class.
I have this component I use to send a message to other users in my chat called SubscriptionService, but you can use any class.
Add this inject to both of your components:
#inject Services.SubscriberService SubscriberService
#region using statements
using DataJuggler.UltimateHelper.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Transactions;
#endregion
namespace BlazorChat.Services
{
#region class SubscriberService
/// <summary>
/// This class is used to subscribe to services, so other windows get a notification a new message
/// came in.
/// </summary>
public class SubscriberService
{
#region Private Variables
private int count;
private Guid serverId;
private List<SubscriberCallback> subscribers;
#endregion
#region Constructor
/// <summary>
/// Create a new instance of a 'SubscriberService' object.
/// </summary>
public SubscriberService()
{
// Create a new Guid
this.ServerId = Guid.NewGuid();
Subscribers = new List<SubscriberCallback>();
}
#endregion
#region Methods
#region BroadcastMessage(SubscriberMessage message)
/// <summary>
/// This method Broadcasts a Message to everyone that ins't blocked.
/// Note To Self: Add Blocked Feature
/// </summary>
public void BroadcastMessage(SubscriberMessage message)
{
// if the value for HasSubscribers is true
if ((HasSubscribers) && (NullHelper.Exists(message)))
{
// Iterate the collection of SubscriberCallback objects
foreach (SubscriberCallback subscriber in Subscribers)
{
// if the Callback exists
if ((subscriber.HasCallback) && (subscriber.Id != message.FromId))
{
// to do: Add if not blocked
// send the message
subscriber.Callback(message);
}
}
}
}
#endregion
#region GetSubscriberNames()
/// <summary>
/// This method returns a list of Subscriber Names ()
/// </summary>
public List<string> GetSubscriberNames()
{
// initial value
List<string> subscriberNames = null;
// if the value for HasSubscribers is true
if (HasSubscribers)
{
// create the return value
subscriberNames = new List<string>();
// Get the SubscriberNamesl in alphabetical order
List<SubscriberCallback> sortedNames = Subscribers.OrderBy(x => x.Name).ToList();
// Iterate the collection of SubscriberService objects
foreach (SubscriberCallback subscriber in sortedNames)
{
// Add this name
subscriberNames.Add(subscriber.Name);
}
}
// return value
return subscriberNames;
}
#endregion
#region Subscribe(string subscriberName)
/// <summary>
/// method returns a message with their id
/// </summary>
public SubscriberMessage Subscribe(SubscriberCallback subscriber)
{
// initial value
SubscriberMessage message = null;
// If the subscriber object exists
if ((NullHelper.Exists(subscriber)) && (HasSubscribers))
{
// Add this item
Subscribers.Add(subscriber);
// return a test message for now
message = new SubscriberMessage();
// set the message return properties
message.FromName = "Subscriber Service";
message.FromId = ServerId;
message.ToName = subscriber.Name;
message.ToId = subscriber.Id;
message.Data = Subscribers.Count.ToString();
message.Text = "Subscribed";
}
// return value
return message;
}
#endregion
#region Unsubscribe(Guid id)
/// <summary>
/// This method Unsubscribe
/// </summary>
public void Unsubscribe(Guid id)
{
// if the value for HasSubscribers is true
if ((HasSubscribers) && (Subscribers.Count > 0))
{
// attempt to find this callback
SubscriberCallback callback = Subscribers.FirstOrDefault(x => x.Id == id);
// If the callback object exists
if (NullHelper.Exists(callback))
{
// Remove this item
Subscribers.Remove(callback);
// create a new message
SubscriberMessage message = new SubscriberMessage();
// set the message return properties
message.FromId = ServerId;
message.FromName = "Subscriber Service";
message.Text = callback.Name + " has left the conversation.";
message.ToId = Guid.Empty;
message.ToName = "Room";
// Broadcast the message to everyone
BroadcastMessage(message);
}
}
}
#endregion
#endregion
#region Properties
#region Count
/// <summary>
/// This property gets or sets the value for 'Count'.
/// </summary>
public int Count
{
get { return count; }
set { count = value; }
}
#endregion
#region HasSubscribers
/// <summary>
/// This property returns true if this object has a 'Subscribers'.
/// </summary>
public bool HasSubscribers
{
get
{
// initial value
bool hasSubscribers = (this.Subscribers != null);
// return value
return hasSubscribers;
}
}
#endregion
#region ServerId
/// <summary>
/// This property gets or sets the value for 'ServerId'.
/// </summary>
public Guid ServerId
{
get { return serverId; }
set { serverId = value; }
}
#endregion
#region Subscribers
/// <summary>
/// This property gets or sets the value for 'Subscribers'.
/// </summary>
public List<SubscriberCallback> Subscribers
{
get { return subscribers; }
set { subscribers = value; }
}
#endregion
#endregion
}
#endregion
}
For my chat application, I want it available to all instances, so in your configure services method of Startup.cs, add a Sington:
services.AddSingleton<SubscriberService>();
To make it only available to this browser instance:
services.AddScoped(SubscriberService);
Now from both components you can call a method or get to properties on your injected class:
SubscriptionService.GetSubscribers();
Or if you prefer interfaces, I wrote a blog post about that and I don't want to duplicate the text:
https://datajugglerblazor.blogspot.com/2020/01/how-to-use-interfaces-to-communicate.html
The inject way is pretty cool though, as your entire application can communicate with other user instances for chat.

Related

System.Reflection.TargetInvocationException in constructor

I am keep getting
System.Reflection.TargetInvocationException
and PresentationFramework.dll, additional info Exception has been thrown by the target of an invocation.
Can someone please help me out here?
Info:
Call Stack
PresentationFramework.dll!System.Windows.Markup.WpfXamlLoader.Load(System.Xaml.XamlReader xamlReader, System.Xaml.IXamlObjectWriterFactory writerFactory, bool skipJournaledProperties, object rootObject, System.Xaml.XamlObjectWriterSettings settings, System.Uri baseUri) Unknown
namespace PMD.Analysis.AnalysisViewModel
{
using PMD.Measurement.MeasurementModel;
using System.Windows.Data;
using PMD.Analysis.AnalysisModel;
using System;
using System.Collections.Generic;
using PMD.Measurement.MeasurementViewModel;
public class AnalysisViewModel : ViewModel
{
/// <summary>
/// New analysis command.
/// </summary>
private ICommand newAnalysis = null;
public PMD.Analysis.AnalysisViewModel.NewAnalysisViewModel m_NewAnalysisViewModel;
Measurement measurement = new Measurement();
private ICollectionView measurements = null;
/// <summary>
/// Measurement's search by title field.
/// </summary>
private string searchTitle;
/// <summary>
/// Measurement's search by title field.
/// </summary>
private string searchTester;
/// <summary>
/// Measurement's search by vehicle VIN field.
/// </summary>
private string searchVehicleVIN;
public MeasurementModel MeasurementModel
{
get;
set;
}
public enum SelectedState
{
// No Masurements.
Inactive,
// Masurements.
Active,
// Waiting for Masurements.
WaitingAnswer
};
public SelectedState CurrentSelectedState { get; set; }
public Analysis Analysis
{
get;
set;
}
public AnalysisViewModel()
{
Analysis = new Analysis();
measurements = new ListCollectionView(MeasurementModel.Measurements);
measurements.Filter = new Predicate<object>(SearchCallbackAnalysis);
}
~AnalysisViewModel()
{
}
/// <summary>
/// List of measurements that will be displayed in analysis view.
/// </summary>
public ICollectionView Measurements
{
get { return measurements; }
set { measurements = value; }
}
/// <summary>
/// Gets or sets new analysis command.
/// </summary>
public ICommand NewAnalysis
{
get
{
if (newAnalysis == null)
newAnalysis = new NewAnalysisCommand(this);
return newAnalysis;
}
}
public bool SearchCallbackAnalysis(object item)
{
bool isItemShowed = true;
if ((searchTitle != "") && (searchTitle != null))
isItemShowed &= (((Measurement)item).Title == searchTitle);
if ((searchVehicleVIN != "") && (searchVehicleVIN != null))
isItemShowed &= (((Measurement)item).Vehicle.VehicleVIN == searchVehicleVIN);
if ((SearchTester != "") && (SearchTester != null))
isItemShowed &= (((Measurement)item).Tester == SearchTester);
return isItemShowed;
}
/// <summary>
/// Gets or sets measurement's search by title field.
/// </summary>
public string SearchTitle
{
get
{
return searchTitle;
}
set
{
searchTitle = value;
Measurements.Refresh();
}
}
/// <summary>
/// Gets or sets measurement's search by tester name field.
/// </summary>
public string SearchTester
{
get
{
return searchTester;
}
set
{
searchTester = value;
Measurements.Refresh();
}
}
/// <summary>
/// Gets or sets measurement's search by vehicle VIN field.
/// </summary>
public string SearchVehicleVIN
{
get
{
return searchVehicleVIN;
}
set
{
searchVehicleVIN = value;
Measurements.Refresh();
}
}
}//end AnalysisViewModel
}//end namespace AnalysisViewModel
if i comment in constructor this line of code:
measurements.Filter = new Predicate<object>(SearchCallbackAnalysis);
Everything works fine but i need this line to search in the list.
Additional info:
xamlReader Cannot obtain value of local or argument 'xamlReader' as it is not available at this instruction pointer, possibly because it has been optimized away. System.Xaml.XamlReader
writerFactory Cannot obtain value of local or argument 'writerFactory' as it is not available at this instruction pointer, possibly because it has been optimized away. System.Xaml.IXamlObjectWriterFactory
skipJournaledProperties Cannot obtain value of local or argument 'skipJournaledProperties' as it is not available at this instruction pointer, possibly because it has been optimized away. bool
rootObject Cannot obtain value of local or argument 'rootObject' as it is not available at this instruction pointer, possibly because it has been optimized away. object
settings Cannot obtain value of local or argument 'settings' as it is not available at this instruction pointer, possibly because it has been optimized away. System.Xaml.XamlObjectWriterSettings
baseUri Cannot obtain value of local or argument 'baseUri' as it is not available at this instruction pointer, possibly because it has been optimized away. System.Uri
i have try:
public ICollectionView Measurements
{
get { return measurements; }
set { measurements = value;
measurements.Filter = new Predicate<object>(SearchCallbackAnalysis);
}
}
Now everything works fine. Thank you for try to help me.

Comparing a List<T> with another List<t>

I have been reading on how to compare a list with one annother. I have tried to implement the IEquatable interface. Here is what i have done so far:
/// <summary>
/// A object holder that contains a service and its current failcount
/// </summary>
public class ServiceHolder : IEquatable<ServiceHolder>
{
/// <summary>
/// Constructor
/// </summary>
/// <param name="service"></param>
public ServiceHolder(Service service)
{
Service = service;
CurrentFailCount = 0;
}
public Service Service { get; set; }
public UInt16 CurrentFailCount { get; set; }
/// <summary>
/// Public equal method
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public override bool Equals(object obj)
{
if (obj == null)
{
return false;
}
ServiceHolder tmp = obj as ServiceHolder;
if (tmp == null)
{
return false;
}
else
{
return Equals(tmp);
}
}
/// <summary>
/// Checks the internal components compared to one annother
/// </summary>
/// <param name="serviceHolder"></param>
/// <returns>tru eif they are the same else false</returns>
public bool Equals(ServiceHolder serviceHolder)
{
if (serviceHolder == null)
{
return false;
}
if (this.Service.Id == serviceHolder.Service.Id)
{
if (this.Service.IpAddress == serviceHolder.Service.IpAddress)
{
if (this.Service.Port == serviceHolder.Service.Port)
{
if (this.Service.PollInterval == serviceHolder.Service.PollInterval)
{
if (this.Service.ServiceType == serviceHolder.Service.ServiceType)
{
if (this.Service.Location == serviceHolder.Service.Location)
{
if (this.Service.Name == this.Service.Name)
{
return true;
}
}
}
}
}
}
}
return false;
}
}
and this is where I use it:
private void CheckIfServicesHaveChangedEvent()
{
IList<ServiceHolder> tmp;
using (var db = new EFServiceRepository())
{
tmp = GetServiceHolders(db.GetAll());
}
if (tmp.Equals(Services))
{
StateChanged = true;
}
else
{
StateChanged = false;
}
}
Now when I debug and I put a break point in the equals function it never gets hit.
This leads me to think I have implemented it incorrectly or Im not calling it correctly?
If you want to compare the contents of two lists then the best method is SequenceEqual.
if (tmp.SequenceEquals(Services))
This will compare the contents of both lists using equality semantics on the values in the list. In this case the element type is ServiceHolder and as you've already defined equality semantics for this type it should work just fine
EDIT
OP commented that order of the collections shouldn't matter. For that scenario you can do the following
if (!tmp.Except(Services).Any())
You can compare lists without the order most easily with linq.
List<ServiceHolder> result = tmp.Except(Services).ToList();

Disable save button when validation fails

As you can likely see from the title, I am about to ask something which has been asked many times before. But still, after reading all these other questions, I cannot find a decent solution to my problem.
I have a model class with basic validation:
partial class Player : IDataErrorInfo
{
public bool CanSave { get; set; }
public string this[string columnName]
{
get
{
string result = null;
if (columnName == "Firstname")
{
if (String.IsNullOrWhiteSpace(Firstname))
{
result = "Geef een voornaam in";
}
}
if (columnName == "Lastname")
{
if (String.IsNullOrWhiteSpace(Lastname))
{
result = "Geef een familienaam in";
}
}
if (columnName == "Email")
{
try
{
MailAddress email = new MailAddress(Email);
}
catch (FormatException)
{
result = "Geef een geldig e-mailadres in";
}
}
if (columnName == "Birthdate")
{
if (Birthdate.Value.Date >= DateTime.Now.Date)
{
result = "Geef een geldige geboortedatum in";
}
}
CanSave = true; // this line is wrong
return result;
}
}
public string Error { get { throw new NotImplementedException();} }
}
This validation is done everytime the property changes (so everytime the user types a character in the textbox):
<TextBox Text="{Binding CurrentPlayer.Firstname, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Width="137" IsEnabled="{Binding Editing}" Grid.Row="1"/>
This works perfect. The validation occurs (the PropertyChanged code for the binding is done in the VM on the CurrentPlayer property, which is an object of Player).
What I would like to do now is disable the save button when the validation fails.
First of all, the easiest solutions seems to be found in this thread:
Enable Disable save button during Validation using IDataErrorInfo
If I want to follow the accepted solution, I'd have to write my
validation code twice, as I cannot simply use the indexer. Writing
double code is absolutely not what I want, so that's not a solution
to my problem.
The second answer on that thread sounded very promising as first,
but the problem is that I have multiple fields that have to be
validated. That way, everything relies on the last checked property
(so if that field is filled in correctly, CanSave will be true, even
though there are other fields which are still invalid).
One more solution I've found is using an ErrorCount property. But as I'm validating at each property change (and so at each typed character), this isn't possible too - how could I know when to increase/decrease the ErrorCount?
What would be the best way to solve this problem?
Thanks
This article http://www.asp.net/mvc/tutorials/older-versions/models-%28data%29/validating-with-the-idataerrorinfo-interface-cs moves the individual validation into the properties:
public partial class Player : IDataErrorInfo
{
Dictionary<string, string> _errorInfo;
public Player()
{
_errorInfo = new Dictionary<string, string>();
}
public bool CanSave { get { return _errorInfo.Count == 0; }
public string this[string columnName]
{
get
{
return _errorInfo.ContainsKey(columnName) ? _errorInfo[columnName] : null;
}
}
public string FirstName
{
get { return _firstName;}
set
{
if (String.IsNullOrWhiteSpace(value))
_errorInfo.AddOrUpdate("FirstName", "Geef een voornaam in");
else
{
_errorInfo.Remove("FirstName");
_firstName = value;
}
}
}
}
(you would have to handle the Dictionary AddOrUpdate extension method). This is similar to your error count idea.
I've implemented the map approach shown in my comment above, in C# this is called a Dictionary in which I am using anonymous methods to do the validation:
partial class Player : IDataErrorInfo
{
private delegate string Validation(string value);
private Dictionary<string, Validation> columnValidations;
public List<string> Errors;
public Player()
{
columnValidations = new Dictionary<string, Validation>();
columnValidations["Firstname"] = delegate (string value) {
return String.IsNullOrWhiteSpace(Firstname) ? "Geef een voornaam in" : null;
}; // Add the others...
errors = new List<string>();
}
public bool CanSave { get { return Errors.Count == 0; } }
public string this[string columnName]
{
get { return this.GetProperty(columnName); }
set
{
var error = columnValidations[columnName](value);
if (String.IsNullOrWhiteSpace(error))
errors.Add(error);
else
this.SetProperty(columnName, value);
}
}
}
This approach works with Data Annotations. You can also bind the "IsValid" property to a Save button to enable/disable.
public abstract class ObservableBase : INotifyPropertyChanged, IDataErrorInfo
{
#region Members
private readonly Dictionary<string, string> errors = new Dictionary<string, string>();
#endregion
#region Events
/// <summary>
/// Property Changed Event
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
#endregion
#region Protected Methods
/// <summary>
/// Get the string name for the property
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="expression"></param>
/// <returns></returns>
protected string GetPropertyName<T>(Expression<Func<T>> expression)
{
var memberExpression = (MemberExpression) expression.Body;
return memberExpression.Member.Name;
}
/// <summary>
/// Notify Property Changed (Shorted method name)
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="expression"></param>
protected virtual void Notify<T>(Expression<Func<T>> expression)
{
string propertyName = this.GetPropertyName(expression);
PropertyChangedEventHandler handler = this.PropertyChanged;
handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
/// <summary>
/// Called when [property changed].
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="expression">The expression.</param>
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> expression)
{
string propertyName = this.GetPropertyName(expression);
PropertyChangedEventHandler handler = this.PropertyChanged;
handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
#region Properties
/// <summary>
/// Gets an error message indicating what is wrong with this object.
/// </summary>
public string Error => null;
/// <summary>
/// Returns true if ... is valid.
/// </summary>
/// <value>
/// <c>true</c> if this instance is valid; otherwise, <c>false</c>.
/// </value>
public bool IsValid => this.errors.Count == 0;
#endregion
#region Indexer
/// <summary>
/// Gets the <see cref="System.String"/> with the specified column name.
/// </summary>
/// <value>
/// The <see cref="System.String"/>.
/// </value>
/// <param name="columnName">Name of the column.</param>
/// <returns></returns>
public string this[string columnName]
{
get
{
var validationResults = new List<ValidationResult>();
string error = null;
if (Validator.TryValidateProperty(GetType().GetProperty(columnName).GetValue(this), new ValidationContext(this) { MemberName = columnName }, validationResults))
{
this.errors.Remove(columnName);
}
else
{
error = validationResults.First().ErrorMessage;
if (this.errors.ContainsKey(columnName))
{
this.errors[columnName] = error;
}
else
{
this.errors.Add(columnName, error);
}
}
this.OnPropertyChanged(() => this.IsValid);
return error;
}
}
#endregion
}

Solving a library design - centralised configuration reference

Consider the sample code below consisting of a Class Library design and an executable Program using the library.
namespace AppLib
{
/// <summary>
/// Entry point for library. Stage manages all the actors in the logic.
/// </summary>
class StageApp
{
/// <summary>
/// Setting that is looked up by different actors
/// </summary>
public int SharedSetting { get; set; }
/// <summary>
/// Stage managing actors with app logic
/// </summary>
public IEnumerable<Actor> Actors { get { return m_actors.Where(x => x.Execute() > 40).ToArray(); } }
private List<Actor> m_actors = new List<Actor>();
}
/// <summary>
/// An object on the stage. Refers to stage (shared)settings and execute depending on the settings.
/// Hence actor should have reference to stage
/// </summary>
class Actor
{
private StageApp m_StageApp;
private int m_Property;
/// <summary>
/// An actor that needs to refer to stage to know what behavior to execute
/// </summary>
/// <param name="stage"></param>
public Actor(StageApp stage)
{
m_StageApp = stage;
m_Property = new Random().Next();
}
/// <summary>
/// Execute according to stage settings
/// </summary>
/// <returns></returns>
public int Execute()
{
return m_StageApp.SharedSetting * m_Property;
}
}
}
namespace AppExe
{
using AppLib;
class Program
{
static void Main(string[] args)
{
StageApp app = new StageApp();
app.SharedSetting = 5;
// Question: How to add actor to stage?
foreach (var actor in app.Actors)
Console.WriteLine(actor.Execute());
}
}
}
Question
Stage and Actor have circular dependency and seems bad to me.
For example, how should we add actors to stage?
If I let user to create new Actor() themselves,
then they must keep on supplying the Stage.
If I give Actor() an internal constructor and make Stage a factory,
then I lose some of the flexibility for users to do making inherited Actors.
If I make Stage a singleton, then I can only have one set of SharedSetting.
In case the user wants more than one Stage in his AppExe, then it cannot be done.
Is there anyway to redesign the architecture so as to avoid the problems above?
If your functionality is not limited by sharing the StageApp settings between actors, but also will be some other logic. For example when you need to know parent StageApp from Actor and vice versa. I preffer to implement it in this way:
namespace AppLib
{
/// <summary>
/// Entry point for library. Stage manages all the actors in the logic.
/// </summary>
class StageApp
{
/// <summary>
/// Setting that is looked up by different actors
/// </summary>
public int SharedSetting { get; set; }
/// <summary>
/// Stage managing actors with app logic
/// </summary>
public IEnumerable<Actor> Actors { get { return m_actors.Where(x => x.Execute() > 40).ToArray(); } }
private List<Actor> m_actors = new List<Actor>();
public int TotalActorsCount
{
get
{
return m_actors.Count;
}
}
public void AddActor(Actor actor)
{
if (actor == null)
throw new ArgumentNullException("actor");
if (m_actors.Contains(actor))
return; // or throw an exception
m_actors.Add(actor);
if (actor.Stage != this)
{
actor.Stage = this;
}
}
// we are hiding this method, to avoid because we can change Stage only to another non null value
// so calling this method directly is not allowed
internal void RemoveActor(Actor actor)
{
if (actor == null)
throw new ArgumentNullException("actor");
if (!m_actors.Contains(actor))
return; // or throuw exception
m_actors.Remove(actor);
}
}
/// <summary>
/// An object on the stage. Refers to stage (shared)settings and execute depending on the settings.
/// Hence actor should have reference to stage
/// </summary>
class Actor
{
private StageApp m_StageApp;
private int m_Property;
public StageApp Stage
{
get
{
return m_StageApp;
}
set
{
if (value == null)
{
throw new ArgumentNullException("value");
}
if (m_StageApp != value)
{
if (m_StageApp != null) // not a call from ctor
{
m_StageApp.RemoveActor(this);
}
m_StageApp = value;
m_StageApp.AddActor(this);
}
}
}
/// <summary>
/// An actor that needs to refer to stage to know what behavior to execute
/// </summary>
/// <param name="stage"></param>
public Actor(StageApp stage)
{
Stage = stage;
m_Property = new Random().Next();
}
/// <summary>
/// Execute according to stage settings
/// </summary>
/// <returns></returns>
public int Execute()
{
return m_StageApp.SharedSetting * m_Property;
}
}
}
namespace AppExe
{
using AppLib;
class Program
{
static void Main(string[] args)
{
StageApp app = new StageApp();
app.SharedSetting = 5;
StageApp anotherApp = new StageApp();
anotherApp.SharedSetting = 6;
// actor is added to the stage automatically after instantiation
Actor a1 = new Actor(app);
Actor a2 = new Actor(app);
Actor a3 = new Actor(anotherApp);
Console.WriteLine("Actors in anotherApp before moving actor:");
Console.WriteLine(anotherApp.TotalActorsCount);
// or by calling method from StageApp class
anotherApp.AddActor(a1);
Console.WriteLine("Actors in anotherApp after calling method (should be 2):");
Console.WriteLine(anotherApp.TotalActorsCount);
// or by setting Stage through property
a2.Stage = anotherApp;
Console.WriteLine("Actors in anotherApp after setting property of Actor instance (should be 3):");
Console.WriteLine(anotherApp.TotalActorsCount);
Console.WriteLine("Actors count in app (should be empty):");
Console.WriteLine(app.TotalActorsCount);
}
}
}
It allows to you to manipulate with object relationships transparently, but requires a little bit mor code to implement.
How about adding a new class "ActorRole" that defines the behaviour of the actor in each Stage. It lets you decouple Actor and Stage from each other, so you can instantiate both independently (through a factory for example) and then combine them creating ActorRole objects that configure your stages. This combinations can be made using a Builder pattern if it is needed.
If you need to dynamically change your actor behaviour, you can use a Strategy pattern based on the ActorRole class, so depending on the Stage, you can assign to the actor different concrete implementations of its behaviour.
I would solve it by using Func instead of passing in the Stage to the Actor. Like this:
namespace AppExe
{
using AppLib;
class Program
{
static void Main(string[] args)
{
StageApp app = new StageApp();
app.CreateActor();
app.SharedSetting = 5;
foreach (var actor in app.Actors)
Console.WriteLine(actor.Execute());
}
}
}
namespace AppLib
{
class StageApp
{
public int SharedSetting { get; set; }
public IEnumerable<Actor> Actors { get { return m_actors.Where(x => x.Execute() > 40).ToArray(); } }
private List<Actor> m_actors = new List<Actor>();
public void CreateActor()
{
m_actors.Add(new Actor(Executed));
}
private int Executed(int arg)
{
return SharedSetting * arg;
}
}
class Actor
{
private int m_Property;
private Func<int, int> m_executed;
public Actor(Func<int, int> executed)
{
m_executed = executed;
m_Property = new Random().Next();
}
public int Execute()
{
return m_executed(m_Property);
}
}
}
I totally agree with you that circular references is not fun :).
You could also solve this using events, but I like passing functions like callback.

Rx - can/should I replace .NET events with Observables?

Given the benefits of composable events as offered by the Reactive Extensions (Rx) framework, I'm wondering whether my classes should stop pushing .NET events, and instead expose Rx observables.
For instance, take the following class using standard .NET events:
public class Foo
{
private int progress;
public event EventHandler ProgressChanged;
public int Progress
{
get { return this.progress; }
set
{
if (this.progress != value)
{
this.progress = value;
// Raise the event while checking for no subscribers and preventing unsubscription race condition.
var progressChanged = this.ProgressChanged;
if (progressChanged != null)
{
progressChanged(this, new EventArgs());
}
}
}
}
}
Lot of monotonous plumbing.
This class could instead use some sort of observable to replace this functionality:
public class Foo
{
public Foo()
{
this.Progress = some new observable;
}
public IObservable<int> Progress { get; private set; }
}
Far less plumbing. Intention is no longer obscured by plumbing details. This seems beneficial.
My questions for you fine StackOverflow folks are:
Would it good/worthwhile to replace standard .NET events with IObservable<T> values?
If I were to use an observable, what kind would I use here? Obviously I need to push values to it (e.g. Progress.UpdateValue(...) or something).
For #2, the most straightforward way is via a Subject:
Subject<int> _Progress;
IObservable<int> Progress {
get { return _Progress; }
}
private void setProgress(int new_value) {
_Progress.OnNext(new_value);
}
private void wereDoneWithWorking() {
_Progress.OnCompleted();
}
private void somethingBadHappened(Exception ex) {
_Progress.OnError(ex);
}
With this, now your "Progress" can not only notify when the progress has changed, but when the operation has completed, and whether it was successful. Keep in mind though, that once an IObservable has completed either via OnCompleted or OnError, it's "dead" - you can't post anything further to it.
I don't recommend managing your own subscriber list when there are built in subjects that can do that for you. It also removes the need for carrying your own mutable copy of T.
Below is my (commentless) version of your solution:
public class Observable<T> : IObservable<T>, INotifyPropertyChanged
{
private readonly BehaviorSubject<T> values;
private PropertyChangedEventHandler propertyChanged;
public Observable() : this(default(T))
{
}
public Observable(T initalValue)
{
this.values = new BehaviorSubject<T>(initalValue);
values.DistinctUntilChanged().Subscribe(FirePropertyChanged);
}
public T Value
{
get { return this.values.First(); }
set { values.OnNext(value); }
}
private void FirePropertyChanged(T value)
{
var handler = this.propertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs("Value"));
}
public override string ToString()
{
return value != null ? value.ToString() : "Observable<" + typeof(T).Name + "> with null value.";
}
public static implicit operator T(Observable<T> input)
{
return input.Value;
}
public IDisposable Subscribe(IObserver<T> observer)
{
return values.Subscribe(observer);
}
event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
{
add { this.propertyChanged += value; }
remove { this.propertyChanged -= value; }
}
}
I'll keep it short and simple:
yes
BehaviorSubject
:)
Ok guys, seeing as how I think it's at least worth a shot to try this, and seeing as how RX's Subject<T> isn't quite what I'm looking for, I've created a new observable that fits my needs:
Implements IObservable<T>
Implements INotifyPropertyChange to work with WPF/Silverlight binding.
Provides easy get/set semantics.
I call the class Observable<T>.
Declaration:
/// <summary>
/// Represents a value whose changes can be observed.
/// </summary>
/// <typeparam name="T">The type of value.</typeparam>
public class Observable<T> : IObservable<T>, INotifyPropertyChanged
{
private T value;
private readonly List<AnonymousObserver> observers = new List<AnonymousObserver>(2);
private PropertyChangedEventHandler propertyChanged;
/// <summary>
/// Constructs a new observable with a default value.
/// </summary>
public Observable()
{
}
public Observable(T initalValue)
{
this.value = initialValue;
}
/// <summary>
/// Gets the underlying value of the observable.
/// </summary>
public T Value
{
get { return this.value; }
set
{
var valueHasChanged = !EqualityComparer<T>.Default.Equals(this.value, value);
this.value = value;
// Notify the observers of the value.
this.observers
.Select(o => o.Observer)
.Where(o => o != null)
.Do(o => o.OnNext(value))
.Run();
// For INotifyPropertyChange support, useful in WPF and Silverlight.
if (valueHasChanged && propertyChanged != null)
{
propertyChanged(this, new PropertyChangedEventArgs("Value"));
}
}
}
/// <summary>
/// Converts the observable to a string. If the value isn't null, this will return
/// the value string.
/// </summary>
/// <returns>The value .ToString'd, or the default string value of the observable class.</returns>
public override string ToString()
{
return value != null ? value.ToString() : "Observable<" + typeof(T).Name + "> with null value.";
}
/// <summary>
/// Implicitly converts an Observable to its underlying value.
/// </summary>
/// <param name="input">The observable.</param>
/// <returns>The observable's value.</returns>
public static implicit operator T(Observable<T> input)
{
return input.Value;
}
/// <summary>
/// Subscribes to changes in the observable.
/// </summary>
/// <param name="observer">The subscriber.</param>
/// <returns>A disposable object. When disposed, the observer will stop receiving events.</returns>
public IDisposable Subscribe(IObserver<T> observer)
{
var disposableObserver = new AnonymousObserver(observer);
this.observers.Add(disposableObserver);
return disposableObserver;
}
event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
{
add { this.propertyChanged += value; }
remove { this.propertyChanged -= value; }
}
class AnonymousObserver : IDisposable
{
public IObserver<T> Observer { get; private set; }
public AnonymousObserver(IObserver<T> observer)
{
this.Observer = observer;
}
public void Dispose()
{
this.Observer = null;
}
}
}
Usage:
Consuming is nice and easy. No plumbing!
public class Foo
{
public Foo()
{
Progress = new Observable<T>();
}
public Observable<T> Progress { get; private set; }
}
Usage is simple:
// Getting the value works just like normal, thanks to implicit conversion.
int someValue = foo.Progress;
// Setting the value is easy, too:
foo.Progress.Value = 42;
You can databind to it in WPF or Silverlight, just bind to the Value property.
<ProgressBar Value={Binding Progress.Value} />
Most importantly, you can compose, filter, project, and do all the sexy things RX lets you do with IObservables:
Filtering events:
foo.Progress
.Where(val => val == 100)
.Subscribe(_ => MyProgressFinishedHandler());
Automatic unsubscribe after N invocations:
foo.Progress
.Take(1)
.Subscribe(_ => OnProgressChangedOnce());
Composing events:
// Pretend we have an IObservable<bool> called IsClosed:
foo.Progress
.TakeUntil(IsClosed.Where(v => v == true))
.Subscribe(_ => ProgressChangedWithWindowOpened());
Nifty stuff!
Apart from the fact that your existing eventing code could be terser:
public event EventHandler ProgressChanged = delegate {};
...
set {
...
// no need for null check anymore
ProgressChanged(this, new EventArgs());
}
I think by switching to Observable<int> you are just moving complexity from the callee to the caller. What if I just want to read the int?
-Oisin

Categories