Get Type in QuickInfoSource - c#

i have a Class like this.
public static class MyTestClass
{
[MyCustomAttribnute("MoreInformations")
public static string MyProperty => "Sample";
}
I can use the class like this.
public static void Main()
{
var myTest = MyTestClass.MyProperty;
}
Now i create a QuickInfoSource and i can get the text "MyTestClass.MyProperty" when i hover over "MyProperty". But i want do get the Type "MyTestClass" to get the customAttribute of "MyProperty".
anybody knows how to get the Type?
Here is my experimental Code of the "QuickInfoSource" class.
internal class TestQuickInfoSource : IAsyncQuickInfoSource
{
private TestQuickInfoSourceProvider m_provider;
private ITextBuffer m_subjectBuffer;
private Dictionary<string, string> m_dictionary;
public TestQuickInfoSource(TestQuickInfoSourceProvider provider, ITextBuffer subjectBuffer)
{
m_provider = provider;
m_subjectBuffer = subjectBuffer;
//these are the method names and their descriptions
m_dictionary = new Dictionary<string, string>();
m_dictionary.Add("add", "int add(int firstInt, int secondInt)\nAdds one integer to another.");
m_dictionary.Add("subtract", "int subtract(int firstInt, int secondInt)\nSubtracts one integer from another.");
m_dictionary.Add("multiply", "int multiply(int firstInt, int secondInt)\nMultiplies one integer by another.");
m_dictionary.Add("divide", "int divide(int firstInt, int secondInt)\nDivides one integer by another.");
}
public async Task<QuickInfoItem> GetQuickInfoItemAsync(IAsyncQuickInfoSession session, CancellationToken cancellationToken)
{
// Map the trigger point down to our buffer.
SnapshotPoint? subjectTriggerPoint = session.GetTriggerPoint(m_subjectBuffer.CurrentSnapshot);
if (!subjectTriggerPoint.HasValue)
{
return null;
}
ITextSnapshot currentSnapshot = subjectTriggerPoint.Value.Snapshot;
SnapshotSpan querySpan = new SnapshotSpan(subjectTriggerPoint.Value, 0);
//look for occurrences of our QuickInfo words in the span
ITextStructureNavigator navigator = m_provider.NavigatorService.GetTextStructureNavigator(m_subjectBuffer);
TextExtent extent = navigator.GetExtentOfWord(subjectTriggerPoint.Value);
SnapshotSpan span = navigator.GetSpanOfPreviousSibling(querySpan);
string searchText = extent.Span.GetText();
string searchText2 = span.GetText();
foreach (string key in m_dictionary.Keys)
{
int foundIndex = searchText.IndexOf(key, StringComparison.CurrentCultureIgnoreCase);
if (foundIndex > -1)
{
string value;
m_dictionary.TryGetValue(key, out value);
return new QuickInfoItem(session.ApplicableToSpan, value ?? string.Empty);
}
}
return null;
}
private bool m_isDisposed;
public void Dispose()
{
if (!m_isDisposed)
{
GC.SuppressFinalize(this);
m_isDisposed = true;
}
}
}

Related

Polymorphic object creation without IF condition

I have an abstract class like this:
public abstract class Records
{
public string Type;
public string Source;
public int Value;
protected Records(string type, string source, int value)
{
Type = type;
Source = source;
Value = value;
}
}
I would like to create many classes inheriting this class, and filling their Type field with a value coming from a static class like this:
public static class ContentTypesString
{
public static string DocumentNew { get { return "Document - New this Month"; }}
public static string HeadlinesNew { get { return "Headlines - New this Month"; }}
etc...
}
I would like to be able to create those child classes without having a test "if foo == "document" then type = ContentTypesString.DocumentNew" or an equivalent switch case (I really have a lot of cases)
Is there a design pattern that suits my needs?
EDIT : As several people pointed out, i should show how i create my instances.
private delegate SPListItemCollection Query(SPWeb web, DateTime startDate, DateTime endDate);
private readonly Query _queries;
#region Constructors
public QueryHandler(SPWeb web, DateTime startTimeSelectedDate, DateTime endTimeSelectedDate)
{
if (web == null) throw new ArgumentNullException("web");
_web = web;
_startTimeSelectedDate = startTimeSelectedDate;
_endTimeSelectedDate = endTimeSelectedDate;
RecordsList = new List<Records>();
// Query Invocation List
_queries = NumberPagePerMonthQuery.PreparedQuery;
_queries += NumberDocumentsPerMonthQuery.PreparedQuery;
_queries += NumberHeadlinesPerMonthQuery.PreparedQuery;
_queries += NumberLeaderboxPerMonthQuery.PreparedQuery;
_queries += NumberNewsPerMonthQuery.PreparedQuery;
_queries += NumberPagesModifiedPerMonthQuery.PreparedQuery;
_queries += NumberPicturesPerMonthQuery.PreparedQuery;
_queries += NumberTeasingPerMonthQuery.PreparedQuery;
}
#endregion Constructors
#region Public Methods
// what about NullReferenceException ? C#6 : item?.Foreach(item => {}); ?
/*** NO C#6 compiler in VS2012... ***/
public void Queries()
{
foreach (var del in _queries.GetInvocationList())
{
var queryresult =
(SPListItemCollection) del.DynamicInvoke(_web, _startTimeSelectedDate, _endTimeSelectedDate);
RecordsList.Add(new Records(del.Method.Name, _web.Title, queryresult.Count));
}
}
EDIT² :
The solution i chose
public List<IQuery> QueryList { get; } // no delegate anymore, and static classes became implementations of IQuery interface.
#region Constructors
public QueryHandler(SPWeb web, DateTime startTimeSelectedDate, DateTime endTimeSelectedDate)
{
if (web == null) throw new ArgumentNullException("web");
_web = web;
_startTimeSelectedDate = startTimeSelectedDate;
_endTimeSelectedDate = endTimeSelectedDate;
RecordsList = new List<Records>();
QueryList = new List<IQuery>
{
new NumberDocumentsPerMonthQuery(),
new NumberHeadlinesPerMonthQuery(),
new NumberLeaderboxPerMonthQuery(),
new NumberNewsPerMonthQuery(),
new NumberPagePerMonthQuery(),
new NumberPagesModifiedPerMonthQuery(),
new NumberPicturesPerMonthQuery(),
new NumberTeasingPerMonthQuery()
};
}
#endregion Constructors
#region Public Methods
// what about NullReferenceException ? C#6 : item?.Foreach(item => {}); ?
/*** NO C#6 compiler in VS2012... ***/
public void Queries()
{
foreach (var query in QueryList)
{
var queryresult = query.PreparedQuery(_web, _startTimeSelectedDate, _endTimeSelectedDate);
RecordsList.Add(query.CreateRecord(_web.Title, queryresult.Count));
}
}
Record class follow the implementation suggested by #dbraillon
Implementation of IQuery interface were added the method :
public Records CreateRecord(string source, int value)
{
return new ModifiedPagesPerMonthRecord(source, value); //or another child of Record class.
}
And voilĂ . Thank you all for the help.
You want to make collection of records, by string code of object type, and parameters.
One of many way to do it - use builder.
Firstly we need to configurate builder:
var builder = new RecordBuilder()
.RegisterBuilder("document", (source, value) => new Document(source, value))
.RegisterBuilder("headlines", (source, value) => new Headlines(source, value));
here we specify how to build record with code "document" and "headlines".
To build a record call:
builder.Build("document", "source", 1);
Builder code can by something like this
(here we look if we know how to build record of the passed type and make it):
public class RecordBuilder
{
public Records Build(string code, string source, int value)
{
Func<string, int, Records> buildAction;
if (recordBuilders.TryGetValue(code, out buildAction))
{
return buildAction(source, value);
}
return null;
}
public RecordBuilder RegisterBuilder(string code, Func<string, int, Records> buildAction)
{
recordBuilders.Add(code, buildAction);
return this;
}
private Dictionary<string, Func<string, int, Records>> recordBuilders = new Dictionary<string, Func<string, int, Records>> ();
}
public class Document : Records
{
public Document(string source, int value) : base(ContentTypesString.DocumentNew, source, value)
{
}
}
public class Headlines : Records
{
public Headlines(string source, int value) : base(ContentTypesString.HeadlinesNew, source, value)
{
}
}
Is that what you need ?
public abstract class Records
{
public string Type;
public string Source;
public int Value;
protected Records(string type, string source, int value)
{
Type = type;
Source = source;
Value = value;
}
}
public class DocumentRecords : Records
{
public DocumentRecords(string source, int value)
: base(ContentTypesString.DocumentNew, source, value) // use here
{
}
}
public class HeadlinesRecords : Records
{
public HeadlinesRecords(string source, int value)
: base(ContentTypesString.HeadlinesNew, source, value) // use here
{
}
}
public static class ContentTypesString
{
public static string DocumentNew { get { return "Document - New this Month"; } }
public static string HeadlinesNew { get { return "Headlines - New this Month"; } }
}

Comparer for dictionary of System.Enum without boxing

I am facing problem when I am trying to create dictionary, where key is System.Enum. Problem is, that dictionary like this allocates garbage because default EqualityComparer is not one of the best. I tryed to write my own comparer, but without any success. Is it somehow possible?
public enum MyEnum
{
One, Two, Three
}
public Dictionary<Enum, string> dict = new Dictionary<Enum, string>();
public void Test()
{
this.dict.Add(MyEnum.One, "One");
this.dict.Add(MyEnum.Two, "Two");
this.dict.Add(MyEnum.Three, "Three");
string result;
this.dict.TryGetValue(MyEnum.Two, out result); // <-- memory alocation :-(
}
No boxing, no heap allocations. Very fast. No need to write a specific comparer for each enum.
This version will work on any enum as long as its underlying Type isn't more than 32-bits (so byte, ushort, uint are all fine).
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
public sealed class EnumComparer<T> : IEqualityComparer<T>
{
[StructLayout(LayoutKind.Explicit)]
private struct Transformer
{
[FieldOffset(0)]
public T t;
[FieldOffset(0)]
public int int32;
}
public static EnumComparer<T> Default { get; } = new EnumComparer<T>();
private EnumComparer()
{
}
public bool Equals(T a, T b)
{
Transformer aTransformer = new Transformer { t = a };
Transformer bTransformer = new Transformer { t = b };
return aTransformer.int32 == bTransformer.int32;
}
public int GetHashCode(T value)
{
Transformer valueTransformer = new Transformer { t = value };
return valueTransformer.int32.GetHashCode();
}
}
If you want to use it with arrays, you'll probably want to make some extension methods and then you can use it like this:
bool contained = enumArray.Contains(MyEnum.someValue, EnumComparer<MyEnum>.Default);
public static class Extensions
{
public static bool Contains<T>(this T[] array, T value, IEqualityComparer<T> equalityComparer)
{
if (array == null)
{
throw new ArgumentNullException("array");
}
return Extensions.IndexOf<T>(array, value, 0, array.Length, equalityComparer) >= 0;
}
public static int IndexOf<T>(this T[] array, T value, IEqualityComparer<T> equalityComparer)
{
if (array == null)
{
throw new ArgumentNullException("array");
}
return Extensions.IndexOf<T>(array, value, 0, array.Length, equalityComparer);
}
public static int IndexOf<T>(this T[] array, T value, int startIndex, int count, IEqualityComparer<T> equalityComparer)
{
if (array == null)
{
throw new ArgumentNullException("array");
}
if (count < 0 || startIndex < array.GetLowerBound(0) || startIndex - 1 > array.GetUpperBound(0) - count)
{
throw new ArgumentOutOfRangeException();
}
int num = startIndex + count;
for (int i = startIndex; i < num; i++)
{
if (equalityComparer.Equals(array[i], value))
{
return i;
}
}
return -1;
}
}
Seeing your example in the comments, and that this is for Unity, watch this clip from Unite 2016. It talks about using Scriptable Objects instead of enums for dictionary keys.
What you would do is have
public class Program
{
protected Dictionary<ScriptableObject, string> dict = new Dictionary<ScriptableObject, string>();
}
public class ProgramChild1 : Program
{
public void Test()
{
dict.Add(MyEnum1.One.Instance, "One");
dict.Add(MyEnum1.Two.Instance, "Two");
dict.Add(MyEnum1.Three.Instance, "Three");
string result;
dict.TryGetValue(MyEnum1.Two.Instance, out result);
}
}
public class ProgramChild2 : Program
{
public void Test()
{
dict.Add(MyEnum2.Four.Instance, "One");
dict.Add(MyEnum2.Five.Instance, "Two");
dict.Add(MyEnum2.Six.Instance, "Three");
string result;
dict.TryGetValue(MyEnum2.Five.Instance, out result);
}
}
//Each class goes in to its own .cs file, Put them in two folders `MyEnum1` and `MyEnum2`
namespace MyEnum1
{
public class One : ScriptableObject
{
private static One _inst;
public static One Instance
{
get
{
if (!_inst)
_inst = Resources.FindObjectOfType<One>();
if (!_inst)
_inst = CreateInstance<One>();
return _inst;
}
}
}
}
namespace MyEnum1
{
public class Two : ScriptableObject
{
private static Two _inst;
public static Two Instance
{
get
{
if (!_inst)
_inst = Resources.FindObjectOfType<Two>();
if (!_inst)
_inst = CreateInstance<Two>();
return _inst;
}
}
}
}
namespace MyEnum1
{
public class Three : ScriptableObject
{
private static Three _inst;
public static Three Instance
{
get
{
if (!_inst)
_inst = Resources.FindObjectOfType<Three>();
if (!_inst)
_inst = CreateInstance<Three>();
return _inst;
}
}
}
}
namespace MyEnum2
{
public class Four : ScriptableObject
{
private static Four_inst;
public static Four Instance
{
get
{
if (!_inst)
_inst = Resources.FindObjectOfType<Four>();
if (!_inst)
_inst = CreateInstance<Four>();
return _inst;
}
}
}
}
namespace MyEnum2
{
public class Five : ScriptableObject
{
private static Five _inst;
public static Five Instance
{
get
{
if (!_inst)
_inst = Resources.FindObjectOfType<Five>();
if (!_inst)
_inst = CreateInstance<Five>();
return _inst;
}
}
}
}
namespace MyEnum2
{
public class Six : ScriptableObject
{
private static Six _inst;
public static Six Instance
{
get
{
if (!_inst)
_inst = Resources.FindObjectOfType<Six>();
if (!_inst)
_inst = CreateInstance<Six>();
return _inst;
}
}
}
}
Note that the reason we inherit from ScriptableObject is if you wanted to expose a enum to the designer you could then drag and drop the enum value right in the designer, you could not do this if the enums where just basic classes.
public class ProgramChild2 : Program
{
public ScriptableObject SelectedValue;
public void Test()
{
dict.Add(MyEnum2.Four.Instance, "One");
dict.Add(MyEnum2.Five.Instance, "Two");
dict.Add(MyEnum2.Six.Instance, "Three");
string result;
dict.TryGetValue(SelectedValue, out result);
}
}
You should write a class that implements the interface IEqualityComparer.
class MyEnumComparer : IEqualityComparer<MyEnum> { ... }
Which means you need to implement both bool Equals(MyEnum, MyEnum) and int GetHashCode(MyEnum) functions defined by the IEqualityComparer interface.
Finally when you are creating your Dictionary, use the constructor overload that receives an IEqualityComparer instance, like this:
static MyEnumComparer myEnumComparer = new MyEnumComparer();
...
Dictionary<MyEnum, string> dict = new Dictionary<MyEnum, string>(myEnumComparer);
Solution proposed by #Executor do not work on .Net framework or .Net Core
because of this Why can't generic types have explicit layout?
But if replace EnumComparer<T> from this solution with ConvertToLong from Dissecting new generic constraints in C# 7.3 it will give solution working on full frameworks.

c# Dictionary<object, T> lookup value

Not sure how to best phrase this which is probably why I'm having difficulty looking it up. Here is a sample console application to demonstrate my meaning.
class Program
{
static void Main(string[] args)
{
var item1 = new Item("Number");
var item2 = new Item("Number");
var dict = new Dictionary<Item, string>();
dict.Add(item1, "Value");
Console.WriteLine(dict.ContainsKey(item2));
var dict2 = new Dictionary<string, string>();
dict2.Add("Number", "Value");
Console.WriteLine(dict2.ContainsKey("Number"));
Console.Read();
}
class Item
{
readonly string number;
public Item(string number)
{
this.number = number;
}
}
}
In this example dict.ContainsKey(item2) returns false and dict2.ContainsKey("Number") returns true. Can Item be defined in such a way that it would behave like a string? The best I can come up with is
static void Main(string[] args)
{
var item1 = new Item("Number");
var item2 = new Item("Number");
var dict = new Dictionary<string, string>();
dict.Add(item1.ToString(), "Test");
Console.WriteLine(dict.ContainsKey(item2.ToString()));
Console.Read();
}
class Item
{
readonly string number;
public Item(string number)
{
this.number = number;
}
public override string ToString()
{
return number;
}
}
This example is contrived, Item would have more fields and ToString() would joint them all up.
You need to override Equals and GetHashCode. Dictionary use Equals and GetHashCode method to compare keys for equality.
class Item
{
readonly string number;
public Item(string number)
{
this.number = number;
}
public override bool Equals(object obj)
{
return Equals(obj as Item);
}
public override int GetHashCode()
{
// this is c# 6 feature
return number?.GetHashCode() ?? 0;
// If you are not using c# 6, you can use
// return number == null ? 0 : number.GetHashCode();
}
private bool Equals(Item another)
{
if (another == null)
return false;
return number == another.number;
}
}
If you have more than one field, you need to account all fields in the Equals and GetHashCode method.

PostSharp Contracts Range

Is it possible to achieve something like this with PostSharp and Contracts?
public class Something
{
int number = 10;
public void Remove([Range(1,this.number)] int remove)
{
number -= remove;
}
public void Add(int add)
{
number += add;
}
}
C# compiler will not allow you to apply the [Range] attribute in this way - you will receive a build error stating that "an attribute argument must be a constant expression, typeof expression or array creation expression".
The workaround is to create an aspect that accepts a field name as an argument. Then import that field into the aspect, so you can read the current max value.
[Serializable]
public class MyRangeAttribute : LocationContractAttribute,
ILocationValidationAspect<int>,
IInstanceScopedAspect,
IAdviceProvider
{
[NonSerialized]
private object instance;
[NonSerialized]
private string maxValueFieldName;
private int minValue;
public ILocationBinding maxValueFieldBinding;
public MyRangeAttribute(int minValue, string maxValueFieldName)
{
this.minValue = minValue;
this.maxValueFieldName = maxValueFieldName;
}
public Exception ValidateValue(int value, string locationName, LocationKind locationKind)
{
int maxValue = (int) this.maxValueFieldBinding.GetValue(ref this.instance, Arguments.Empty);
if (value < minValue || value > maxValue)
return new ArgumentOutOfRangeException(locationName);
return null;
}
public IEnumerable<AdviceInstance> ProvideAdvices(object targetElement)
{
FieldInfo maxValueField = ((LocationInfo)targetElement).DeclaringType
.GetField( this.maxValueFieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance );
yield return new ImportLocationAdviceInstance(
typeof (MyRangeAttribute).GetField("maxValueFieldBinding"),
new LocationInfo(maxValueField));
}
public object CreateInstance( AdviceArgs adviceArgs )
{
MyRangeAttribute clone = (MyRangeAttribute) this.MemberwiseClone();
clone.instance = adviceArgs.Instance;
return clone;
}
public void RuntimeInitializeInstance()
{
}
}
You can apply this aspect like this:
public class Something
{
private int number = 10;
public void Remove([MyRange(1, "number")] int remove)
{
number -= remove;
}
public void Add(int add)
{
number += add;
}
}

How to pass an object instead of it value

I am trying to make a simple parser where I have key, value pairs that I would like to assign to variables. I want to map a String to a variable.
I think the below test class should express what I would like to do. But how can I store a variable so I can update it?
[TestClass]
public class StringObjectMapperTest
{
[TestMethod]
public void TestMethod1()
{
string string1 = "";
string string2 = "";
StringObjectMapper mapper = new StringObjectMapper();
mapper.Add("STRING1", ref string1);
mapper.Add("STRING2", ref string2);
mapper.Set("STRING1", "text1");
Assert.AreEqual("text1", string1); // FAILS as string1 is still ""
}
}
public class StringObjectMapper
{
private Dictionary<string, string> mapping = new Dictionary<string, string>();
public StringObjectMapper()
{
}
public void Set(string key, string value)
{
string obj = mapping[key];
obj = value;
}
internal void Add(string p, ref string string1)
{
mapping.Add(p, string1);
}
}
Update:
I am trying to use a Boxed String but this also seems to act like a immutable object, any idea why?
[TestClass]
public class StringObjectMapperTest
{
[TestMethod]
public void TestMethod1()
{
BoxedString string1 = "string1";
BoxedString string2 = "string2";
StringObjectMapper mapper = new StringObjectMapper();
mapper.Add("STRING1", ref string1);
mapper.Add("STRING2", ref string2);
mapper.Set("STRING1", "text1");
string s = string1;
Assert.AreEqual("text1", s); // Fails as s = "string1" ???
}
}
public struct BoxedString
{
private string _value;
public BoxedString(string value)
{
_value = value;
}
public void Set(string value)
{
_value = value;
}
static public implicit operator BoxedString(string value)
{
return new BoxedString(value);
}
static public implicit operator string(BoxedString boxedString)
{
return boxedString._value;
}
}
public class StringObjectMapper
{
private Dictionary<string, BoxedString> mapping = new Dictionary<string, BoxedString>();
public StringObjectMapper()
{
}
public void Set(string key, string value)
{
BoxedString obj = mapping[key];
obj.Set(value);
}
internal void Add(string p, ref BoxedString obj)
{
mapping.Add(p, obj);
}
}
You can't do this as strings are immutable. Changing a string returns a new string. Even though you pass the string by reference in Add, the string value stored in the dictionary is still immutable. Here's what happens in Set:
public void Set(string key, string value)
{
string obj = mapping[key]; // obj points to the string value at mapping[key]
obj = value; // obj points to the string value referenced by value - mapping[key] is unchanged.
}
To do what you want you'll need to "box" the string by using a true reference type - whether object or a class that wraps the string as the Dictionary's value type.
this is not what you want but this work... if you accept it :-)
use StringBuilder
using System;
using System.Collections.Generic;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
StringBuilder string1 = new StringBuilder();
StringBuilder string2 = new StringBuilder();
StringObjectMapper mapper = new StringObjectMapper();
mapper.Add("STRING1", ref string1);
mapper.Add("STRING2", ref string2);
mapper.Set("STRING1", "text1");
Console.Write("text1" == string1.ToString());
Console.ReadKey();
}
}
public class StringObjectMapper
{
private Dictionary<string, StringBuilder> mapping = new Dictionary<string, StringBuilder>();
public StringObjectMapper()
{
}
public void Set(string key, string value)
{
StringBuilder obj = mapping[key];
obj.Clear();
obj.Append(value);
}
internal void Add(string p, ref StringBuilder string1)
{
mapping.Add(p, string1);
}
}
}
My first thought is that this isn't possible in C# without obtaining the pointer that ref string string1 represents. Unfortunately, .NET is garbage collected, which means the pointer can change unless you use a fixed block.
I think a better approach here is to use an interface that represents a way to set a string.
public interface IData
{
void Set(string data);
}
public class StringObjectMapper
{
private readonly Dictionary<string, IData> mapping = new Dictionary<string, IData>();
public void Set(string key, string value)
{
if (key == null)
{
throw new ArgumentNullException("key");
}
if (value == null)
{
throw new ArgumentNullException("value");
}
mapping[key].Set(value);
}
internal void Add(string key, IData data)
{
if (key == null)
{
throw new ArgumentNullException("key");
}
if (data == null)
{
throw new ArgumentNullException("data");
}
mapping.Add(key, data);
}
}
I guess the best way to solve this is by using delegates. Although the syntax is still a bit strange to me it seems the cleanest way to solve this. The below codes works:
[TestClass]
public class StringObjectMapperTest
{
private Dictionary<string, Setter> mapping = new Dictionary<string, Setter>();
public delegate void Setter(string v);
[TestMethod]
public void TestMethod1()
{
string string1 = "string1";
string string2 = "string2";
string text1 = "text1";
string text2 = "text2";
Add("STRING1", x => string1 = x);
Add("STRING2", x => string2 = x);
Assert.AreNotEqual(text1, string1);
Set("STRING1", text1);
Assert.AreEqual(text1, string1);
Assert.AreNotEqual(text2, string2);
Set("STRING2", text2);
Assert.AreEqual(text2, string2);
}
private void Set(string key, string value)
{
Setter set = mapping[key];
set(value);
}
private void Add(string p, Setter del)
{
mapping.Add(p, del);
}
}

Categories

Resources