C# - Intercepting property changes in subclasses - c#

I'm in the process of creating a framework in which I provide the base class and the implementers of the framework will inherit from the base class and provide additional properties and methods. In the base class, I would like to have a way of observing when a property value is changed. The property can be from the base class or in any of the subclasses. I know that through reflection, I can determine the list of properties from any instance, but is there a way I can track the property changing value?
Here is a very simplistic example of what I am saying:
public class BaseClass
{
public string BaseClassProperty { get; set; }
public void DoSomethingWhenEitherPropertyGetsChanged()
{
}
}
public class SubClass : BaseClass
{
public string SubClassProperty { get; set; }
}
What can I do to have DoSomethingWhenEitherPropertyGetsChanged get executed when either of the properties has it's value changed.

You can use notifypropertyweaver for this purpose. It does exactly what you want. Here's a link:
notifypropertyweaver
From the open source home page:
Uses IL weaving (via http://www.mono-project.com/Cecil) to inject INotifyPropertyChanged code into properties.
No attributes required
No references required
No base class required
Supports .net 3.5, .net 4, Silverlight 3, Silverlight 4, Silverlight 5 and Windows Phone 7
Supports client profile mode

I would probably use Postsharp and create an inherited attribute injecting interception code into all public properties. Marking the attribute as inherited should also attach it to all subclasses automatically.

I wrote my own idea of your requirements, but I am not sure if it suits your needs. INotifyProperty changed is something you could also look into, but I don't really like it because it is like wiring up speghetti. Maybe this will give you some creative ideas, though.
What this does, is allow you to use ObservableObject as for all of your properties types. By doing this, each property will have an ObjectChanged event you can wire-up to. The con(s) are that you must initialize all of your properties in the constructor to prevent a NullReferenceException somewhere in your code.
This example uses three classes.
ObservableObject.cs
Employee.cs
Program.cs
ObservableObject.cs
//-----------------------------------------------------------------------------
// <copyright file="ObservableObject.cs" company="DCOM Productions">
// Copyright (c) DCOM Productions. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------------
namespace PropertyChangedEventExample {
using System;
public class ObservableObject : Object {
/// <summary>
/// Expose the default constructor
/// </summary>
public ObservableObject() {
// No default implementation
}
private object m_Object = null;
/// <summary>
/// Base object
/// </summary>
public object Object {
get {
return m_Object;
}
set {
if (m_Object != value) {
m_Object = value;
OnObjectChanged(this, EventArgs.Empty);
}
}
}
/// <summary>
/// Triggered when the value of this object has changed.
/// </summary>
public event System.EventHandler<EventArgs> ObjectChanged;
/// <summary>
/// EventHandler wire-up
/// </summary>
protected virtual void OnObjectChanged(object sender, System.EventArgs e) {
if (ObjectChanged != null) {
ObjectChanged(sender, e);
}
}
/// <summary>
/// Gets the value
/// </summary>
public object Get() {
return this.Object;
}
/// <summary>
/// Sets the value
/// </summary>
public void Set(object value) {
this.Object = value;
}
}
}
Employee.cs
//-----------------------------------------------------------------------------
// <copyright file="Employee.cs" company="DCOM Productions">
// Copyright (c) DCOM Productions. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------------
namespace PropertyChangedEventExample {
using System;
public class Employee {
/// <summary>
/// Expose default constructor
/// </summary>
public Employee() {
Name = new ObservableObject();
}
/// <summary>
/// Gets or sets the name
/// </summary>
public ObservableObject Name {
get;
set;
}
}
}
Program.cs
//-----------------------------------------------------------------------------
// <copyright file="Program.cs" company="DCOM Productions">
// Copyright (c) DCOM Productions. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------------
namespace PropertyChangedEventExample {
using System;
class Program {
static void Main(string[] args) {
Employee employee = new Employee();
employee.Name.Set("David");
employee.Name.ObjectChanged += new EventHandler<EventArgs>(Name_ObjectChanged);
employee.Name.Set("Dave");
Console.ReadKey(true);
}
static void Name_ObjectChanged(object sender, EventArgs e) {
ObservableObject employee = sender as ObservableObject;
Console.WriteLine("Name changed to {0}", employee.Get());
}
}
}

Your best bet would be what CrisWue recommended and use postsharp or some other post-processor to inject the behavior in your properties. Other than that I think you would need to call DoSomethingWhenEitherPropertyGetsChanged() manually within your properties.
If you are creating a library that is consumed by people other than you or your organization, a post-processor may not be the right way to go as it adds the 3rd party tool as another requirement to their build process.

Related

Method description from an interface [duplicate]

Suppose I have this interface
public interface IFoo
{
///<summary>
/// Foo method
///</summary>
void Foo();
///<summary>
/// Bar method
///</summary>
void Bar();
///<summary>
/// Situation normal
///</summary>
void Snafu();
}
And this class
public class Foo : IFoo
{
public void Foo() { ... }
public void Bar() { ... }
public void Snafu() { ... }
}
Is there a way, or is there a tool that can let me automatically put in the comments of each member in a base class or interface?
Because I hate re-writing the same comments for each derived sub-class!
You can always use the <inheritdoc /> tag:
public class Foo : IFoo
{
/// <inheritdoc />
public void Foo() { ... }
/// <inheritdoc />
public void Bar() { ... }
/// <inheritdoc />
public void Snafu() { ... }
}
Using the cref attribute, you can even refer to an entirely different member in an entirely different class or namespace!
public class Foo
{
/// <inheritdoc cref="System.String.IndexOf" />
public void Bar() { ... } // this method will now have the documentation of System.String.IndexOf
}
Use /// <inheritdoc/> if you want inheritance. Avoid GhostDoc or anything like that.
I agree it is annoying that comments are not inherited. It would be a fairly simple add-in to create if someone had the time (i wish i did).
That said, in our code base we put XML comments on the interfaces only and add extra implementation comments to the class. This works for us as our classes are private/internal and only the interface is public. Any time we use the objects via the interfaces we have full comments display in intellisence.
GhostDoc is good start and has made the process easier to write comments. It is especially useful keeping comments up-to-date when you add/remove parameters, re-run GhostDoc and it will update the description.
GhostDoc does exactly that. For methods which aren't inherited, it tries to create a description out of the name.
FlingThing() becomes "Flings the Thing"
I would say to directly use the
/// <inheritdoc cref="YourClass.YourMethod"/> --> For methods inheritance
And
/// <inheritdoc cref="YourClass"/> --> For directly class inheritance
You have to put this comments just on the previous line of your class/method
This will get the info of your comments for example from an interface that you have documented like :
/// <summary>
/// This method is awesome!
/// </summary>
/// <param name="awesomeParam">The awesome parameter of the month!.</param>
/// <returns>A <see cref="AwesomeObject"/> that is also awesome...</returns>
AwesomeObject CreateAwesome(WhateverObject awesomeParam);
Java has this, and I use it all the time. Just do:
/**
* {#inheritDoc}
*/
And the Javadoc tool figures it out.
C# has similar marker:
<inheritDoc/>
You can read more here:
http://www.ewoodruff.us/shfbdocs/html/79897974-ffc9-4b84-91a5-e50c66a0221d.htm
Another way is to use the <see /> XML documentation tag.
This is some extra effort but works out of the box...
Here are some examples:
/// <summary>
/// Implementation of <see cref="IFoo"/>.
/// </summary>
public class Foo : IFoo
{
/// <summary>
/// See <see cref="IFoo"/>.
/// </summary>
public void Foo() { ... }
/// <summary>
/// See <see cref="IFoo.Bar"/>
/// </summary>
public void Bar() { ... }
/// <summary>
/// This implementation of <see cref="IFoo.Snafu"/> uses the a caching algorithm for performance optimization.
/// </summary>
public void Snafu() { ... }
}
Update:
I now prefer to use /// <inheritdoc/> which is now supported by ReSharper.
ReSharper has an option to copy the comments from the base class or interface.
I ended up creating a tool to post-process the XML documentation files to add support for replacing the <inheritdoc/> tag in the XML documentation files themselves. Available at www.inheritdoc.io (free version available).
Well, there is a kind of native solution, I found for .NET Core 2.2
The idea is to use <include> tag.
You can add <GenerateDocumentationFile>true</GenerateDocumentationFile> your .csproj a file.
You might have an interface:
namespace YourNamespace
{
/// <summary>
/// Represents interface for a type.
/// </summary>
public interface IType
{
/// <summary>
/// Executes an action in read access mode.
/// </summary>
void ExecuteAction();
}
}
And something that inherits from it:
using System;
namespace YourNamespace
{
/// <summary>
/// A type inherited from <see cref="IType"/> interface.
/// </summary>
public class InheritedType : IType
{
/// <include file='bin\Release\netstandard2.0\YourNamespace.xml' path='doc/members/member[#name="M:YourNamespace.IType.ExecuteAction()"]/*'/>
public void ExecuteAction() => Console.WriteLine("Action is executed.");
}
}
Ok, it is a bit scary, but it does add the expected elements to the YourNamespace.xml.
If you build Debug configuration, you can swap Release for Debug in the file attribute of include tag.
To find a correct member's name to reference just open generated Documentation.xml file.
I also assume that this approach requires a project or solution to be build at least twice (first time to create an initial XML file, and the second time to copy elements from it to itself).
The bright side is that Visual Studio validates copied elements, so it is much easier to keep documentation and code in sync with interface/base class, etc (for example names of arguments, names of type parameters, etc).
At my project, I have ended up with both <inheritdoc/> (for DocFX) and <include/> (For publishing NuGet packages and for validation at Visual Studio):
/// <inheritdoc />
/// <include file='bin\Release\netstandard2.0\Platform.Threading.xml' path='doc/members/member[#name="M:Platform.Threading.Synchronization.ISynchronization.ExecuteReadOperation(System.Action)"]/*'/>
public void ExecuteReadOperation(Action action) => action();
End the question:
This feature has been added at VS2019 v16.4.
https://developercommunity.visualstudio.com/t/608809#T-N875117
It works on the interfeace and abstruct class overrideable members

exposing .Net to COM

I have some .Net functionality I am trying to use in VB6. I have followed several tutorials. I wrote a test program with success using the formula here: http://www.codeproject.com/Articles/3511/Exposing-NET-Components-to-COM
However, when I try to do it with my actual project, my ProgId doesn't show in the registry like my test file. I made sure property ComVisible == true
using System.Runtime.InteropServices;
namespace Controls.Graph.Web
{
[Guid("5F9F6C6F-016A-4CFF-BD7A-3463761807E1")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface _GraphScript
{
[DispId(1)]
string getGraphXml();
}
[Guid("35901BC6-EFF1-490C-84FA-786E8462C553")]
[ClassInterface(ClassInterfaceType.None)]
[ProgId(ProgIds.GraphScript)]
public class GraphScript : _GraphScript
{
protected GraphScript()
{
}
/// <summary>
///
/// </summary>
/// <returns>The graphs xml and javascript</returns>
public string getGraphXml()
{
DisplayDefaults tempDefaults;
tempDefaults = new DisplayDefaults();
GraphConstructor graph = new GraphConstructor();
graph.constructGraph();
GraphModel completedGraph = graph.Graph;
return GraphControl.RenderGraph(completedGraph, tempDefaults, 1) + GraphControl.RenderGraphScript();
}
}
}
and my progid...
using System;
namespace Controls.Graph.Web
{
/// <summary>
/// ProgID Constant
/// </summary>
public static class ProgIds
{
public const string GraphScript = "GraphData";
}
}
I'm not sure which piece of the puzzle I'm missing here
EDIT: actually the Guid shows up in the registry however the Progid still is not. Any ideas/suggestions?
also made sure to do this:
I have figured out what was wrong. I needed to change some access modifiers to PUBLIC -- including my GraphScript() constructor.

Serializing a collection and comply to Code Analysis

While running Code Analysis on an existing project I came across the messages Do not expose generic lists and Collection properties should be read only.
However, this class is used to read/write from/to an xml configuration file.
Is it possible to make this class comply to CA1002 and CA2227 or do I have to suppress these rules for XML-related classes (there are a lot of them in the project)?
EDIT
Changing List<string> to Collection<string> solved CA1002.
Still no clue on how to solve CA2227 and still be able to (de)serialize the whole thing.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Xml.Serialization;
/// <summary>
/// Class containing the Configuration Storage
/// </summary>
[XmlRoot("Configuration")]
public class ConfigurationStorage
{
/// <summary>
/// Gets or sets the list of executers.
/// </summary>
[XmlArray("Executers")]
[XmlArrayItem("Executer")]
public Collection<string> Executers { get; set; }
/// <summary>
/// Gets or sets the list of IPG prefixes.
/// </summary>
[XmlArray("IpgPrefixes")]
[XmlArrayItem("IpgPrefix")]
public Collection<string> IpgPrefixes { get; set; }
}
Reading the xml-file:
public static ConfigurationStorage LoadConfiguration()
{
if (File.Exists(ConfigFile))
{
try
{
using (TextReader r = new StreamReader(ConfigFile))
{
var s = new XmlSerializer(typeof(ConfigurationStorage));
var config = (ConfigurationStorage)s.Deserialize(r);
return config;
}
}
catch (InvalidOperationException invalidOperationException)
{
throw new StorageException(
"An error occurred while deserializing the configuration XML file.", invalidOperationException);
}
}
}
How about:
/// <summary>
/// Class containing the Configuration Storage
/// </summary>
[XmlRoot("Configuration")]
public class ConfigurationStorage {
/// <summary>
/// Gets or sets the list of executers.
/// </summary>
[XmlArray("Executers")]
[XmlArrayItem("Executer")]
public Collection<string> Executers { get; private set; }
/// <summary>
/// Gets or sets the list of IPG prefixes.
/// </summary>
[XmlArray("IpgPrefixes")]
[XmlArrayItem("IpgPrefix")]
public Collection<string> IpgPrefixes { get; private set; }
public ConfigurationStorage() {
Executers = new Collection<string>();
IpgPrefixes = new Collection<string>();
}
}
This will still work for xml serialization/deserialization.
If you read the documentation on MSDN, you see a note:
The XmlSerializer gives special treatment to classes that implement
IEnumerable or ICollection. A class that implements IEnumerable must
implement a public Add method that takes a single parameter. The Add
method's parameter must be of the same type as is returned from the
Current property on the value returned from GetEnumerator, or one of
that type's bases. A class that implements ICollection (such as
CollectionBase) in addition to IEnumerable must have a public Item
indexed property (indexer in C#) that takes an integer, and it must
have a public Count property of type integer. The parameter to the Add
method must be the same type as is returned from the Item property, or
one of that type's bases. For classes that implement ICollection,
values to be serialized are retrieved from the indexed Item property,
not by calling GetEnumerator.
So, I think, if you fall in line with this special treatment, you'll have better code that works with XmlSerializer, doesn't use a legacy namespace, and satisfies Code Analysis warnings in the right way, rather than exploiting an oversight in the rule.
using System;
using System.Collections.Generic;
using System.Xml.Serialization;
/// <summary>
/// Class containing the Configuration Storage
/// </summary>
[XmlRoot("Configuration")]
public class ConfigurationStorage
{
// The executers.
private readonly ICollection<string> executers = new List<string>();
// The IPG prefixes.
private readonly ICollection<string> ipgPrefixes = new List<string>();
/// <summary>
/// Gets the list of executers.
/// </summary>
[XmlArray("Executers")]
[XmlArrayItem("Executer")]
public ICollection<string> Executers
{
get
{
return this.executers;
}
}
/// <summary>
/// Gets the list of IPG prefixes.
/// </summary>
[XmlArray("IpgPrefixes")]
[XmlArrayItem("IpgPrefix")]
public ICollection<string> IpgPrefixes
{
get
{
return this.ipgPrefixes;
}
}
}

How is the Memento Pattern implemented in C#4?

The Memento Pattern itself seems pretty straight forward. I'm considering implementing the same as the wikipedia example, but before I do are there any language features of C# that make it easier to implement or use?
One obvious feature would be generics, implementing an generic memento will allow you to use it for any object you want.
Many examples that you will see will use a string (including all those currently among the replies to this question) as state which is a problem since it's one of the few types in .NET which are immutable.
When dealing with mutable objects (like any reference type with a setter-property) you have to remember though that when you save the memento you need to create a deepcopy of the object. Otherwise whenever you change your original object you will change your memento.
You could do this by using a serializer like protobuf-net or json.net since they don't require you to mark your objects with serializable attribute like the normal .net serialization mechanism does.
Codeproject have few articles about generic memento implementations, but they tend to skip the deepcopy part:
Generic Memento Pattern for Undo-Redo in C#
Memento Design Pattern
I'm not aware of any already built-in way to support Memento pattern.
I see a couple of implementations by using .NET Mock frameworks, where in practise a clone of the object is created and can be field with a data, but I consider it kind of overhead.
The use Memento patter on Undo/Redo usually, probably you too. In this case, it's better to have as less data on Undo/Redo stack as possible, so the custom undoable object is something that I would go for.
Hope this helps.
There is one thing that will make this pattern marginally quicker to write in C# and that is that any state fields can be declared as public readonly so you don't need properties or 'get' methods to access them.
Here is a straight conversion with public readonly included.
class Originator
{
private string state;
// The class could also contain additional data that is not part of the
// state saved in the memento.
public void Set(string state)
{
Console.WriteLine("Originator: Setting state to " + state);
this.state = state;
}
public Memento SaveToMemento()
{
Console.WriteLine("Originator: Saving to Memento.");
return new Memento(state);
}
public void RestoreFromMemento(Memento memento)
{
state = memento.SavedState;
Console.WriteLine("Originator: State after restoring from Memento: " + state);
}
public class Memento
{
public readonly string SavedState;
public Memento(string stateToSave)
{
SavedState = stateToSave;
}
}
}
class Caretaker
{
static void Main(string[] args)
{
List<Originator.Memento> savedStates = new List<Originator.Memento>();
Originator originator = new Originator();
originator.Set("State1");
originator.Set("State2");
savedStates.Add(originator.SaveToMemento());
originator.Set("State3");
// We can request multiple mementos, and choose which one to roll back to.
savedStates.Add(originator.SaveToMemento());
originator.Set("State4");
originator.RestoreFromMemento(savedStates[1]);
}
}
I've found one using Generics here:
#region Originator
public class Originator<T>
{
#region Properties
public T State { get; set; }
#endregion
#region Methods
/// <summary>
/// Creates a new memento to hold the current
/// state
/// </summary>
/// <returns>The created memento</returns>
public Memento<T> SaveMemento()
{
return (new Memento<T>(State));
}
/// <summary>
/// Restores the state which is saved in the given memento
/// </summary>
/// <param name="memento">The given memento</param>
public void RestoreMemento(Memento<T> memento)
{
State = memento.State;
}
#endregion
}
#endregion
#region Memento
public class Memento<T>
{
#region Properties
public T State { get; private set; }
#endregion
#region Ctor
/// <summary>
/// Construct a new memento object with the
/// given state
/// </summary>
/// <param name="state">The given state</param>
public Memento(T state)
{
State = state;
}
#endregion
}
#endregion
#region Caretaker
public class Caretaker<T>
{
#region Properties
public Memento<T> Memento { get; set; }
#endregion
}
#endregion
#region Originator
public class Originator<T>
{
#region Properties
public T State { get; set; }
#endregion
#region Methods
/// <summary>
/// Creates a new memento to hold the current
/// state
/// </summary>
/// <returns>The created memento</returns>
public Memento<T> SaveMemento()
{
return (new Memento<T>(State));
}
/// <summary>
/// Restores the state which is saved in the given memento
/// </summary>
/// <param name="memento">The given memento</param>
public void RestoreMemento(Memento<T> memento)
{
State = memento.State;
}
#endregion
}
#endregion
#region Memento
public class Memento<T>
{
#region Properties
public T State { get; private set; }
#endregion
#region Ctor
/// <summary>
/// Construct a new memento object with the
/// given state
/// </summary>
/// <param name="state">The given state</param>
public Memento(T state)
{
State = state;
}
#endregion
}
#endregion
#region Caretaker
public class Caretaker<T>
{
#region Properties
public Memento<T> Memento { get; set; }
#endregion
}
#endregion
Used like this:
Originator<string> org = new Originator<string>();
org.State = "Old State";
// Store internal state in the caretaker object
Caretaker<string> caretaker = new Caretaker<string>();
caretaker.Memento = org.SaveMemento();
Console.WriteLine("This is the old state: {0}", org.State);
org.State = "New state";
Console.WriteLine("This is the new state: {0}", org.State);
// Restore saved state from the caretaker
org.RestoreMemento(caretaker.Memento);
Console.WriteLine("Old state was restored: {0}", org.State);
// Wait for user
Console.Read();
As #Simon Skov Boisen mentions this will only work for immutable data and requires a deep copy.

Creating PowerShell Automatic Variables from C#

I trying to make automatic variables available to Excel VBA (like ActiveSheet or ActiveCell) also available to PowerShell as 'automatic variables'. PowerShell engine is hosted in an Excel VSTO add-in and Excel.Application is available to it as Globals.ThisAddin.Application. I found this thread here on StackOverflow and started created PSVariable derived classes like:
public class ActiveCell : PSVariable
{
public ActiveCell(string name) : base(name) { }
public override object Value
{
get
{
return Globals.ThisAddIn.Application.ActiveCell;
}
}
}
public class ActiveSheet : PSVariable
{
public ActiveSheet(string name) : base(name) { }
public override object Value
{
get
{
return Globals.ThisAddIn.Application.ActiveSheet;
}
}
}
and adding their instances to the current POwerShell session:
runspace.SessionStateProxy.PSVariable.Set(new ActiveCell("ActiveCell"));
runspace.SessionStateProxy.PSVariable.Set(new ActiveSheet("ActiveSheet"));
This works and I am able to use those variables from PowerShell as $ActiveCell and $ActiveSheet (their value change as Excel active sheet or cell change). Then I read PSVariable documentation here and saw this:
"There is no established scenario for deriving from this class. To programmatically create a shell variable, create an instance of this class and set it by using the PSVariableIntrinsics class."
As I was deriving from PSVariable, I tried to use what was suggested:
PSVariable activeCell = new PSVariable("ActiveCell");
activeCell.Value = Globals.ThisAddIn.Application.ActiveCell;
runspace.SessionStateProxy.PSVariable.Set(activeCell);
Using this, $ActiveCell appears in my PowerShell session, but its value doesn't change as I change the active cell in Excel.
Is the above comment from PSVariable documentation something I should worry about, or I can continue creating PSVariable derived classes? Is there another way of making Excel globals available to PowerShell?
Our documentation is wrong - it is a supported scenario.
Here's a bit more about the technique:
http://poshcode.org/2198
http://www.leeholmes.com/blog/2009/03/26/more-tied-variables-in-powershell/
http://www.pavleck.net/powershell-cookbook/ch03.html
Lee Holmes [MSFT]
Windows PowerShell Development
Obviously in your second example, where you are not deriving from PSVariable, you couldn't expect the $ActiveCell variable to change with the value of the ActiveCell property since you're capturing its value just once.
I don't believe deriving from PSVariable is a supported scenario, but it does work and I've done it to add variables such as $Now and $Today.
It might be a better idea to just expose an $Application variable to PowerShell script instead of the various properties of the Application object. The upside to this is that you wouldn't need to create a bunch of automatic variables and PowerShell scripts could access anything the Application object has to offer by using $Application.ActiveCell. The other benefit is that it doesn't need to be an automatic variable at all because the Application object reference will never change.
Having said all that, I've included a subclass of PSVariable that I use from time to time which takes a ScriptBlock for the getter and setter. This lets me define automatic variables from PowerShell without needing a separate derived class for each one.
using System;
using System.Management.Automation;
namespace Einstein.PowerShell
{
public sealed class DynamicVariable : PSVariable
{
#region Constructors
/// <summary>
/// </summary>
public DynamicVariable(string name, ScriptBlock onGet)
: this(name, onGet, null)
{
}
/// <summary>
/// </summary>
public DynamicVariable(string name, ScriptBlock onGet, ScriptBlock onSet)
: base(name, null, ScopedItemOptions.AllScope)
{
OnGet = onGet;
OnSet = onSet;
}
#endregion
#region Properties
/// <summary>
/// The ScriptBlock that runs to get the value of the variable.
/// </summary>
private ScriptBlock OnGet
{
get;
set;
}
/// <summary>
/// The ScriptBlock that runs to get the value of the variable.
/// </summary>
private ScriptBlock OnSet
{
get;
set;
}
/// <summary>
/// Gets or sets the underlying value of the variable.
/// </summary>
public override object Value
{
get
{
if (OnGet == null) {
return null;
}
return OnGet.Invoke();
}
set
{
if (OnSet == null) {
throw new InvalidOperationException("The variable is read-only.");
}
OnSet.Invoke(value);
}
}
#endregion
}
}

Categories