I have a lot of duplicate code places:
if (claimSettingHistoryDto.NewClaimTypeName == claimSettingHistoryDto.OldClaimTypeName)
{
claimSettingHistoryDto.NewClaimTypeName = null;
claimSettingHistoryDto.OldClaimTypeName = null;
}
if (claimSettingHistoryDto.NewApplicantName == claimSettingHistoryDto.OldApplicantName)
{
claimSettingHistoryDto.NewApplicantName = null;
claimSettingHistoryDto.OldApplicantName = null;
}
if (claimSettingHistoryDto.NewDamageSparePartsTotalCostInsertion == claimSettingHistoryDto.OldDamageSparePartsTotalCostInsertion)
{
claimSettingHistoryDto.NewDamageSparePartsTotalCostInsertion = null;
claimSettingHistoryDto.OldDamageSparePartsTotalCostInsertion = null;
}
and so constantly for different classes of different fields
I wish I had a feature like this:
private void SetNull(object newData, object oldData)
{
if (newData == oldData)
{
newData = null;
oldData = null;
}
}
but of course I understand that this is not true, since I only change the local value inside the function. How do I change the class field?
There are multiple ways of doing that, with varying positions on the "good idea" to "bad idea" spectrum.
Fields as ref parameters (good idea)
(...) this is not true, since I only change the local value inside the function
You're wrong, because ref and out parameters allow you to change values non-locally.
If you have access to the actual fields, you can pass them as a ref parameter:
public class Dto
{
private string? _old;
private string? _new;
public string? Old => _old;
public string? New => _new;
public void Foo() {
SetNullIfEqual(ref _new, ref _old);
}
private static void SetNullIfEqual<T>(ref T? newData, ref T? oldData) where T: class
{
if (newData == oldData)
{
newData = null;
oldData = null;
}
}
}
More info on passing as reference here.
This won't work with properties, even if they have a default setter. Properties are not fields, they're methods in disguise. If you can't access the actual fields...
Properties as delegates (meh idea)
... having access to properties only you'd need to pass them as delegates like this:
public class Dto
{
public string? Old { get; set; }
public string? New { get; set; }
}
public class Outside
{
public void Foo(Dto dto) {
SetNullIfEqual(() => dto.New, () => dto.Old, v => dto.New = v, v => dto.Old = v);
}
private static void SetNullIfEqual<T>(
Func<T?> getNew,
Func<T?> getOld,
Action<T?> setNew,
Action<T?> setOld) where T: class
{
if (getNew() == getOld())
{
setNew(null);
setOld(null);
}
}
}
This is clunky though, you have to question how much space it'd actually save. An instance method working on fields as in the first suggestion works much better.
When you have reflection everything looks like a nail (probably bad idea)
You can also do this with reflection, which will remove all safety, give much worse performance, but the absolute most flexibility.
using System.Reflection;
public class Dto
{
public string? Old { get; set; }
public string? New { get; set; }
}
public class Outside
{
public void Foo(Dto dto) {
SetNullIfEqual(nameof(dto.New), nameof(dto.Old), dto);
}
private static void SetNullIfEqual<T>(
string newPropName,
string oldPropName,
T instance)
{
PropertyInfo newProp = typeof(T).GetProperty(newPropName);
PropertyInfo oldProp = typeof(T).GetProperty(oldPropName);
if (Equals(newProp.GetValue(instance), oldProp.GetValue(instance)))
{
newProp.SetValue(instance, null);
oldProp.SetValue(instance, null);
}
}
}
I removed all error handling for brevity.
Recommendation
I'd go with the fields-as-ref-parameters way. If the method in question lives outside of the type, so it can't have access to the fields (don't ever use public fields please), I'd just move it into the type. In your case it'd be a bunch of methods called SetClaimTypeName, SetApplicantName, etc.
Related
I'm struggling migrating from protobuf-net v2.4.6 to v3.0.100 (or any 3.0.x) in regards to an existing type hierarchy used as ProtoContracts with one of the subtypes requiring a surrogate due to one of its property being of type object.
With previous configuration in place, I get the following exception thrown on creating the runtime model:
System.InvalidOperationException: 'Types with surrogates cannot be used in inheritance hierarchies'
Hence, my question is how to properly deal with this scenario using protobuf-net 3.0.x?
Here's my (over-)simplified repro of the issue:
class Program
{
static void Main(string[] args)
{
var model = RuntimeTypeModel.Create();
_ = model[typeof(Base)]; // <-- InvalidOperationException thrown here
Base value = new Complex();
var copy = model.DeepClone(value);
}
}
[ProtoContract]
[ProtoInclude(1, typeof(Simple))]
[ProtoInclude(2, typeof(Complex))]
public abstract class Base
{
}
[ProtoContract]
public class Simple : Base
{
}
[ProtoContract(Surrogate = typeof(ComplexSurrogate))]
public class Complex : Base
{
}
[ProtoContract(Name = nameof(Complex))]
public class ComplexSurrogate
{
[ProtoConverter]
public static ComplexSurrogate Convert(Complex source) => new ComplexSurrogate();
[ProtoConverter]
public static Complex Convert(ComplexSurrogate source) => new Complex();
}
As a side note: When compiling protobuf-net from source with the above mentioned exception suppressed, I'm able to defined a surrogate for the Base class which seems to serve as a workaround.
Right now, that scenario isn't supported. While reworking the code for v3, some ambiguous outcomes/intents were found, and it needs work to go in and figure out what the correct outcomes are in each case, design how to achieve that, implement it, and test it. That time has not yet been found, so right now it is safer to prevent a configuration that could lead to big problems downstream, than to just shrug and assume that whatever happens is correct. It is on my list of things to do, but: ultimately this is a project that comes entirely out of my own spare time - it isn't sponsored or part of my paid work, so: it'll get there when it gets there.
I encountered the same error in protobuf v3, and I solved that with custom serializer.
My base class is
[ProtoContract]
[ProtoInclude(500, typeof(XXXRequest))]
[ProtoInclude(501, typeof(XXXResponse))]
// ...
public class MessageBase
{
[ProtoMember(1)]
long ID { get; internal set; }
[ProtoMember(3)]
int ExecutionMilliseconds { get; set; }
}
Its equivalent proto is
message Message {
int64 ID = 1;
int32 ExecutionMilliseconds = 3;
oneof body {
PredictBonusRequest XXXRequest = 500;
PredictBonusResponse XXXResponse = 501;
// ...
}
}
I want to replace some types (e.g. XXXResponse) to use the contract-first class instead. This would allow us to migrate from code-first to contract-first smoothly.
For sub-types should be surrogated, we create custom serializer as below.
using ProtoBuf;
using ProtoBuf.Serializers;
using UnderlyingMessage = GeneratedProto.Contract.Message;
using UnderlyingResponse = GeneratedProto.Contract.XXXResponse;
[DataContract]
[Serializable]
[ProtoContract(Serializer = typeof(XXXResponseSerializer))]
public class XXXResponse : MessageBase
{
class XXXResponseSerializer : ISerializer<XXXResponse>
{
public SerializerFeatures Features => SerializerFeatures.CategoryMessage | SerializerFeatures.WireTypeString;
public XXXResponse Read(ref ProtoReader.State state, XXXResponse value)
{
ISerializer<UnderlyingMessage> serializer = state.GetSerializer<UnderlyingMessage>();
return serializer.Read(ref state, value);
}
public void Write(ref ProtoWriter.State state, XXXResponse value)
{
ISerializer<UnderlyingMessage> serializer = state.GetSerializer<UnderlyingMessage>();
serializer.Write(ref state, value);
}
}
private readonly UnderlyingResponse _resp;
public XXXResponse() : this(new UnderlyingResponse() { })
{
}
private XXXResponse(UnderlyingResponse msg)
{
_resp = msg;
}
public static implicit operator XXXResponse(UnderlyingMessage value)
{
if( value != null)
{
return new XXXResponse(value.XXXResponse)
{
ID = value.ID,
ExecutionMilliseconds = value.ExecutionMilliseconds,
};
}
return null;
}
public static implicit operator UnderlyingMessage(XXXResponse value)
{
if(value != null)
{
return new UnderlyingMessage()
{
ID = value.ID,
ExecutionMilliseconds = value.ExecutionMilliseconds,
XXXResponse = value._resp,
};
}
return null;
}
public Transaction[] Transactions
{
get { return _resp.Transactions?.Select(t => (Transaction)t)?.ToArray(); }
set { _resp.Transactions = value?.Select(t => (BE.Data.Contract.Transaction)t)?.ToList(); }
}
public long DomainID { get { return _resp.DomainID; } set { _resp.DomainID = value; } }
public string UniversalID { get { return _resp.UniversalID; } set { _resp.UniversalID = value; } }
public string ExtraData { get { return _resp.ExtraData; } set { _resp.ExtraData = value; } }
// other proxied fields ...
}
The key is, when ISerializer.Read or ISerializer.Write is fired, the wire-format is from the scope of the entrie message, including all fields of base class, and current sub-type is in a field whose number is identified by ProtoInclude.
In our case this works. For other sub-types which we don't want surrogate at this moment, it still works as it did.
Setup:
public class Data
{
public int A { get; set; }
public int B { get; set; }
}
public class Runner
{
public static void Run(Data data)
{
data.A = data.B;
data.A = 1;
}
}
class Program
{
static void Main(string[] args)
{
var data = new Data() { A = 1, B = 2 };
Runner.Run(data);
}
}
Problem: I need to implement change tracking here for property names not values. Inside Runner.Run on the first line data.A = data.B I need to record somehow that "A" was set to "B" (literally property names) and then on the next line data.A = 1 I need to record that "A" was set to constant and say forget about it.
Constrains:
When setting one property to another (e.g. A = B) that needs to be recorded
When setting property to anything else (e.g. A = 1 or A = B * 2) this change needs to be forgotten (e.g. remember A only)
Suppose this is the tracker contract being used:
void RecordChange(string setterName, string getterName);
void UnTrackChange(string setterName);
Question:
I would like to somehow proxy the Data class so it still can be used in the interface code (e.g. Runner - is a whole bunch of a business logic code that uses Data) INCLUDING strong-typing and it can track it's changes without modifying the code (e.g. there is lots of places like 'data.A = data.B').
Is there any way to do it without resorting to I guess some magic involving IL generation?
Already investigated/tried:
PostSharp interceptors/Castle.DynamicProxy with interceptors - these alone cannot help. The most I can get out of it is to have a value of data.B inside setter interceptor but not nameof(data.B).
Compiler services - haven't found anything suitable here - getting the name of caller doesn't really help.
Runtine code generation - smth like proxy inherited from DynamicObject or using Relfection.Emit (TypeBuilder probably) - I lose typings.
Current solution:
Use the Tracker implementation of the abovementioned contract and pass it around into every function down the road. Then instead of writing data.A = data.B use method tracker.SetFrom(x => x.A, x => x.B) - tracker holds a Data instance and so this works. BUT in a real codebase it is easy to miss something and it just makes it way less readable.
It is the closest the solution I've come up with. It isn't perfect as I still need to modify all the contracts/methods in the client code to use a new data model but at least all the logic stays the same.
So I'm open for other answers.
Here's the renewed Data model:
public readonly struct NamedProperty<TValue>
{
public NamedProperty(string name, TValue value)
{
Name = name;
Value = value;
}
public string Name { get; }
public TValue Value { get; }
public static implicit operator TValue (NamedProperty<TValue> obj)
=> obj.Value;
public static implicit operator NamedProperty<TValue>(TValue value)
=> new NamedProperty<TValue>(null, value);
}
public interface ISelfTracker<T>
where T : class, ISelfTracker<T>
{
Tracker<T> Tracker { get; set; }
}
public class NamedData : ISelfTracker<NamedData>
{
public virtual NamedProperty<int> A { get; set; }
public virtual NamedProperty<int> B { get; set; }
public Tracker<NamedData> Tracker { get; set; }
}
Basically I've copy-pasted the original Data model but changed all its properties to be aware of their names.
Then the tracker itself:
public class Tracker<T>
where T : class, ISelfTracker<T>
{
public T Instance { get; }
public T Proxy { get; }
public Tracker(T instance)
{
Instance = instance;
Proxy = new ProxyGenerator().CreateClassProxyWithTarget<T>(Instance, new TrackingNamedProxyInterceptor<T>(this));
Proxy.Tracker = this;
}
public void RecordChange(string setterName, string getterName)
{
}
public void UnTrackChange(string setterName)
{
}
}
The interceptor for Castle.DynamicProxy:
public class TrackingNamedProxyInterceptor<T> : IInterceptor
where T : class, ISelfTracker<T>
{
private const string SetterPrefix = "set_";
private const string GetterPrefix = "get_";
private readonly Tracker<T> _tracker;
public TrackingNamedProxyInterceptor(Tracker<T> proxy)
{
_tracker = proxy;
}
public void Intercept(IInvocation invocation)
{
if (IsSetMethod(invocation.Method))
{
string propertyName = GetPropertyName(invocation.Method);
dynamic value = invocation.Arguments[0];
var propertyType = value.GetType();
if (IsOfGenericType(propertyType, typeof(NamedProperty<>)))
{
if (value.Name == null)
{
_tracker.UnTrackChange(propertyName);
}
else
{
_tracker.RecordChange(propertyName, value.Name);
}
var args = new[] { propertyName, value.Value };
invocation.Arguments[0] = Activator.CreateInstance(propertyType, args);
}
}
invocation.Proceed();
}
private string GetPropertyName(MethodInfo method)
=> method.Name.Replace(SetterPrefix, string.Empty).Replace(GetterPrefix, string.Empty);
private bool IsSetMethod(MethodInfo method)
=> method.IsSpecialName && method.Name.StartsWith(SetterPrefix);
private bool IsOfGenericType(Type type, Type openGenericType)
=> type.IsGenericType && type.GetGenericTypeDefinition() == openGenericType;
}
And the modified entry point:
static void Main(string[] args)
{
var data = new Data() { A = 1, B = 2 };
NamedData namedData = Map(data);
var proxy = new Tracker<NamedData>(namedData).Proxy;
Runner.Run(proxy);
Console.ReadLine();
}
The Map() function actually maps Data to NamedData filling in property names.
Question
How do I define an incoming Type T constraint that will allow me to call a static method on the class (of type T) to get the intended IndexModel object for passing to Mongo?
Background
I'm currently trying to write a Mongo Provider class that will allow me to ensure my particular database and collection are present before doing any operations with them, since there is a potential that the container or server it resides in could be destroyed and recreated at any time, and I'd prefer to have a safe way in code to ensure that the external dependency is there (instance is beyond my control, so I have to trust that something is there).
One of the things I'm trying to do, since I've managed to do what I stated above for Database and Collection instantiation, is to also generate indexes. My idea was to have a static method on the classes that would return their specific definition of an index model. This way, each class would be responsible for their own Mongo indexes, rather than some convoluted switch-case statement in my Provider based on the incoming type of T.
My first idea was to have an interface that shared this method, but Interfaces don't allow you to declare a static method. Similarly, I tried an Abstract Base-class and found that the static implementation would call the base class that defined the method, rather than any overrides in an inheritor.
Sample Code
public class MyClass
{
public DateTime DateValue { get; set; }
public int GroupId { get; set; }
public string DataType { get; set; }
public static IEnumerable<CreateIndexModel<MyClass>> GetIndexModel(IndexKeysDefinitionBuilder<MyClass> builder)
{
yield return new CreateIndexModel<MyClass>(
builder.Combine(
builder.Descending(entry => entry.DateValue),
builder.Ascending(entry => entry.GroupId),
builder.Ascending(entry => entry.DataType)
)
);
}
}
Edit
I guess I should probably include a shell of my Mongo Provider class. See below:
Edit #2 due to questions about how this hasn't solved my problem, I'm updating the MongoProvider to have the problematic code. Note: Once this method is included, the class will no longer compile, since it isn't possible given what I've done thus far.
public class MongoProvider
{
private readonly IMongoClient _client;
private MongoPrivder(ILookup<string, string> lookup, IMongoClient client)
{
_client = client;
foreach(var database in lookup)
foreach(var collection in database)
Initialize(database.Key, collection);
}
public MongoProvider(IConfiguration config) :this(config.GetMongoObjects(), config.GetMongoClient())
{}
public MongoProvider(IConfiguration config, IMongoClient client) : this(config.GetMongoObjects(), client)
{}
private void Initialize(string database, string collection)
{
var db = _client.GetDatabase(database);
if (!db.ListCollectionNames().ToList().Any(name => name.Equals(collection)))
db.CreateCollection(collection);
}
// The Problem
private void InitializeIndex<T>(string database, string collection)
{
IEnumerable<CreateIndexModel<T>> models;
switch (T)
{
case MyClass:
model = MyClass.GetIndexModel();
break;
default:
break;
}
await _client.GetDatabase(database)
.GetCollection<T>(collection)
.Indexes
.CreateManyAsync(models);
}
}
Edit #3
As a stop-gap, I've gone ahead and done something terrible (not sure if it's going to work yet), and I'll supply the example so you can know my best solution thus far.
public static class Extensions
{
#region Object Methods
public static T TryCallMethod<T>(this object obj, string methodName, params object[] args) where T : class
{
var method = obj.GetType().GetMethod(methodName);
if (method != null)
{
return method.Invoke(obj, args) as T;
}
return default;
}
#endregion
}
This allows me to do the following (inside of MongoProvider)
private async void InitializeIndex<T>(string database, string collection) where T : new()
{
var models = new T().TryCallMethod<IEnumerable<CreateIndexModel<T>>>("GetIndexModel");
await _client.GetDatabase(database)
.GetCollection<T>(collection)
.Indexes
.CreateManyAsync(models);
}
Since it doesn't look like I'm going to get an answer to this, I figured I would provide my solution for future searches of this question. Basically, I added an extension method to the base object class, and used reflection to determine if the method I was looking for was there. From there, I returned a value of true or false, depending on if the method was found, and output the return value to a parameter, in the traditional TryGet pattern.
Note to Future Readers
I do not recommend this approach. This is just how I solved my problem for accessing a method on a type of T. Ideally, an instance method would be implemented, and a signature defined in a common Interface, but that wasn't going to work for my use case.
My Answer
public static class Extensions
{
#region Object Methods
public static bool TryCallMethod<T>(this object obj, string methodName, out T result, params object[] args) where T : class
{
result = null;
var method = obj.GetType().GetMethod(methodName);
if (method == null)
return false;
result = method.Invoke(obj, args) as T;
return true;
}
#endregion
}
My data class looks like this (obfuscated from actual usage)
[BsonDiscriminator("data")]
public class DataClass
{
#region Private Fields
private const string MongoCollectionName = "Data";
#endregion
#region Public Properties
public string CollectionName => MongoCollectionName;
[BsonId]
public ObjectId Id { get; set; }
[BsonElement("date_value")]
public DateTime DateValue { get; set; }
[BsonElement("group_id")]
public int GroupId { get; set; }
[BsonElement("data_type")]
public string DataType { get; set; }
[BsonElement("summary_count")]
public long SummaryCount { get; set; }
[BsonElement("flagged_count")]
public long FlaggedCount { get; set; }
[BsonElement("error_count")]
public long ErrorCount { get; set; }
#endregion
#region Constructor
public DataClass()
{
}
public DataClass(int groupId, string dataType = null, long summaryCount = 0, long flaggedCount = 0, long errorCount = 0)
{
Id = ObjectId.GenerateNewId();
DateValue = DateTime.UtcNow;
GroupId = groupId;
DocCount = summaryCount;
DataType = dataType ?? "default_name";
FlaggedCount = flaggedCount;
ErrorCount = errorCount;
}
#endregion
#region Public Methods
public static IEnumerable<CreateIndexModel<AuditEntry>> GetIndexModel(IndexKeysDefinitionBuilder<AuditEntry> builder)
{
yield return new CreateIndexModel<AuditEntry>(
builder.Combine(
builder.Descending(entry => entry.DateValue),
builder.Ascending(entry => entry.GroupId),
builder.Ascending(entry => entry.DataType)
)
);
}
#endregion
}
I would then call the method in the following fashion, inside my MongoProvider class. The ellipses are present to identify that more code exists within the class.
public class MongoProvider : IMongoProvider
{
#region Private Fields
private readonly IMongoClient _client;
#endregion
#region Constructor
...
#endregion
#region Private Methods
private void Initialize(string database, string collection)
{
var db = _client.GetDatabase(database);
if (!db.ListCollectionNames().ToList().Any(name => name.Equals(collection)))
db.CreateCollection(collection);
}
private async Task InitializeIndex<T>(string database, string collection) where T : new()
{
if(new T().TryCallMethod<IEnumerable<CreateIndexModel<T>>>("GetIndexModel", out var models, new IndexKeysDefinitionBuilder<T>()))
await _client.GetDatabase(database)
.GetCollection<T>(collection)
.Indexes
.CreateManyAsync(models);
}
private static void ValidateOptions<T>(ref FindOptions<T, T> options)
{
if(options != null)
return;
options = new FindOptions<T, T>
{
AllowPartialResults = null,
BatchSize = null,
Collation = null,
Comment = "AspNetWebService",
CursorType = CursorType.NonTailable,
MaxAwaitTime = TimeSpan.FromSeconds(10),
MaxTime = TimeSpan.FromSeconds(10),
Modifiers = null,
NoCursorTimeout = false,
OplogReplay = null
};
}
private static FilterDefinition<T> GetFilterDefinition<T>(Func<FilterDefinitionBuilder<T>, FilterDefinition<T>>[] builders)
{
if(builders.Length == 0)
builders = new Func<FilterDefinitionBuilder<T>, FilterDefinition<T>>[] {b => b.Empty};
return new FilterDefinitionBuilder<T>()
.And(builders
.Select(b => b(new FilterDefinitionBuilder<T>()))
);
}
#endregion
#region Public Methods
public async Task<IReadOnlyCollection<T>> SelectManyAsync<T>(string database, string collection, FindOptions<T, T> options = null, params Func<FilterDefinitionBuilder<T>, FilterDefinition<T>>[] builders) where T : new()
{
ValidateOptions(ref options);
await InitializeIndex<T>(database, collection);
var filter = GetFilterDefinition(builders);
var find = await _client.GetDatabase(database)
.GetCollection<T>(collection)
.FindAsync(filter, options);
return await find.ToListAsync();
}
...
#endregion
}
I am creating a project data pipeline and I need to return different types from a single class
in this class I have a number of Dictionaries that hold and separate the elements/content i want to load up but i need a way to return them with a single string... as i am not to familiar with these Type functions i am lost as to how to return the content properly
I need this in a separate class so i can do a XML serialization later
Here is what I have now
DataClass contents;
public T ReturnType<T>(string asset)
{
if(typeof(T) == typeof(int))
{
return contents.Integers[Asset];
}
if(typeof(T) == typeof(float))
{
return contents.Floats[Asset];
}
if(typeof(T) == typeof(double))
{
return contents.Doubles[Asset];
}
return default(T);
}
it will allow me to use a base Object class to parse the content but i dont want anything to get lost in transit so i am weary in using this method
my question is how to return one of the different objects of a certain types within the class that i am using for serialization with a function like that
If i wanted to use the previous function to grab content within the class eg
public Object someobject;
//button event handler to change the current object
//preferably this would be changed depending on the object i would be calling
//but this should do for showing how it is supposed to work
public void ChangeCurrentObject(event e)
{
someobject = (Object)ReturnType<Object>("23rdObject");
}
it sends a string to the function called 'ReturnType' and returns an object ie(int, float,etc) within there own respective dictionary
The generics in this case will only help you not to write diferent method for every asset type. You can also use this aproach to make it more modular.
static class Assets
{
public interface IAssetHandler<out T>
{
T GetAsset(string name);
}
private static readonly Dictionary<Type,object> _handlers=new Dictionary<Type, object>();
public static T GetAsset<T>(string name)
{
object assetHandler;
if(!_handlers.TryGetValue(typeof(T),out assetHandler))
{
throw new Exception("No handler for that type of asset");
}
return (assetHandler as IAssetHandler<T>).GetAsset(name);
}
public static void RegisterAssetHandler<T>(IAssetHandler<T> handler)
{
_handlers[typeof (T)] = handler;
}
}
public class IntAssetsHandler:Assets.IAssetHandler<int>
{
#region Implementation of IAssetHandler<out int>
public int GetAsset(string name)
{
return 0;
}
#endregion
}
static void Main(string[] args)
{
Assets.RegisterAssetHandler(new IntAssetsHandler());
Console.WriteLine(Assets.GetAsset<int>("test"));
}
You could use external class, set the properties types as you wish, then use it in your function.
public class MultipleOpjects
{
public List<string> ObjectOne { get; set; }
public List<object> ObjectTwo { get; set; }
public object ObjectThree { get; set; }
}
public MultipleOpjects GetAnything()
{
MultipleOpjects Vrble = new MultipleOpjects();
Vrble.ObjectOne = SomeThing1;
Vrble.ObjectTwo = SomeThing2;
Vrble.ObjectThree = SomeThing3;
return Vrble;
}
Consider the following control (snipped for brevity):
public partial class ConfigurationManagerControl : UserControl
{
public Func<string, bool> CanEdit { get; set;}
public Func<string, bool> CanDelete { get; set; }
public Dictionary<string, string> Settings
{
get { return InnerSettings; }
set
{
InnerSettings = value;
BindData();
}
}
private Dictionary<string, string> InnerSettings;
private void OnListIndexChanged(object sender, EventArgs e)
{
this.EditButton.Enabled = false;
this.DeleteButton.Enabled = false;
var indices = this.List.SelectedIndices;
if (indices.Count != 1)
{
return;
}
var index = indices[0];
var item = this.List.Items[index];
if (this.CanEdit != null)
{
this.EditButton.Enabled = this.CanEdit(item.Text);
}
if (this.CanDelete != null)
{
this.DeleteButton.Enabled = this.CanDelete(item.Text);
}
}
}
There's more to this control, but suffice it to say that it allows a user to add, edit, and delete the entries in a Dictionary<string, string>. In order to determine whether or not it should allow the user to edit or delete the entries, it uses the delegate method properties, CanDelete and CanEdit, which are provided by the form or control that hosts it:
public class SetupWizard : Form
{
public SetupWizard()
{
InitializeComponent();
this.SettingManager.CanEdit = CanEditSetting;
this.SettingManager.CanDelete = CanDeleteSetting;
}
private static bool CanEditSetting(string item)
{
var lockedSettings = new[] { "LicenseHash", "ProductHash" };
return !lockedSettings.Contains(item.ToLower());
}
private static bool CanDeleteSetting(string item)
{
var lockedSettings = new[] {
"LicenseHash",
"ProductHash",
"UserName",
"CompanyName"
};
return !lockedSettings.Contains(item.ToLower());
}
}
I find that this design is both satisfactory and worrisome at the same time. On the one hand, it seems to solve the problem using the simplest solution that works (it certainly separates the concerns nicely). On the other hand, I have this nagging concern that I am using delegates improperly and should be using an event, instead (even though I do not need multiple listeners, and only need the caller to tell me if the item is editable).
And then, on the other other hand, there's the chance that there's a completely different design that I haven't even considered that might solve the problem in a vastly superior way.
So. Is this design technically correct, maintainable, and flexible? Or should I be doing something better?
I suggest the use of an interface with these two methods. That's a lot cleaner:
interface ICantThinkOfAGoodName
{
bool CanEdit(string item);
bool CanDelete(string item);
}
You could create something similar to the RelayCommand used in many MVVM frameworks:
public class RelayObject : ICantThinkOfAGoodName
{
public RelayObject() : this(null, null) {}
public RelayObject(Func<string, bool> canEdit, Func<string, bool> canDelete)
{
if(canEdit == null) canEdit = s => true;
if(canDelete == null) canDelete = s => true;
_canEdit = canEdit;
_canDelete = canDelete;
}
public bool CanEdit(string item)
{
return _canEdit(item);
}
public bool CanDelete(string item)
{
return _canDelete(item);
}
}
Use it like this:
public SetupWizard()
{
InitializeComponent();
this.SettingManager.PropertyName = new RelayObject(CanEditSetting,
CanDeleteSetting);
// or (all can be deleted)
this.SettingManager.PropertyName = new RelayObject(CanEditSetting, null);
// or (all can be edited)
this.SettingManager.PropertyName = new RelayObject(null, CanDeleteSetting);
// or (all can be edited and deleted)
this.SettingManager.PropertyName = new RelayObject();
}
BTW: I am using Property injection here, because it is a control. Normally, I would pass the ICantThinkOfAGoodName dependency in the constructor of the ConfigurationManagerControl.
It may be this is what #Daniel Hilgarth is suggesting when he says "use an interface" (n.b. - his answer now reflects a more general/flexible approach to implementing the interface). Instead of assigning delegates to your method directly, why not give the control a property, such as DataState or whatever you want to call it, using an interface that encapsulates the information you need, and leave it up to the owner to decide how to implement that.
interface IDataState
{
bool CanEdit(string item);
bool CanDelete(string item);
}
public partial class ConfigurationManagerControl : UserControl
{
public IDataState DataState {get;set;}
// your code checks DataState.CanEdit & DataState.CanDelete
}
public class SetupWizard : Form, IDataState
{
public SetupWizard()
{
InitializeComponent();
SettingManager.DataState =this;
}
public bool CanEdit(string item)
{
... implement directly or return from your private function
}
public bool CanDelete(string item)
{
}
}
But this gives you the flexibility to implement that interface any way you choose, with another object, etc. and it makes it easy to also just pass the owner itself (implementing the interface).