Can an optional parameter be optionally required in C#? - c#

Is there a possibility in C# to have an optional parameter be optionally required in specific situations and have it throw an error on compile time?
Let me explain with the code below as an example. The class ServiceResponse has a constructor accepting an enumeration value and an optional string. In case the enumeration value used to instantiate the class equals Error, the message becomes required. In the example code it will throw an ArgumentNullException when no message was supplied. Sadly this will only become visible on run time. However it should become visible on compile time so it warns the developer.
public class ServiceResponse
{
public ServiceResponse(ServiceResult result, string message = null)
{
Result = result;
Message = result == ServiceResult.Error ? message ?? throw new System.ArgumentNullException(nameof(message)) : message;
}
public string Message { get; }
public ServiceResult Result { get; }
}
public enum ServiceResult {
Ok,
NotFound,
Error
}

I would make the constructor private and expose the 3 static methods required to instantiate.
You can also make the message field in CreateError(message) as NotNull, and some linters will pick this up and treat as a warning.
public class ServiceResponse
{
// Change constructor to private
private ServiceResponse(ServiceResult result, string message)
{
Result = result;
Message = message;
}
public static ServiceResponse CreateOk(string message = null)
{
return new ServiceResponse(ServiceResult.OK, message);
}
public static ServiceResponse CreateNotFound(string message = null)
{
return new ServiceResponse(ServiceResult.NotFound, message);
}
public static ServiceResponse CreateError([NotNull] string message)
{
if (string.IsNullOrEmpty(message))
{
throw new ArgumentNullException(nameof(message));
}
return new ServiceResponse(ServiceResult.Error, message);
}
... Other Class Properties
}

Would static creation methods be an option?
public class ServiceResponse
{
private ServiceResponse(ServiceResult result, string message = null)
{
Result = result;
Message = message;
}
public string Message { get; }
public ServiceResult Result { get; }
public static ServiceResponse CreateInfo(ServiceResult result, string message = null)
{
return new ServiceResponse(result, message);
}
public static ServiceResponse CreateError(string message)
{
return new ServiceResponse(ServiceResult.Error, message);
}
}
This doesn't prevent passing null to CreateError, but the developer probaly won't miss the message by accident.

The OneOf library might be able to help here.
public abstract class ServiceResponse
: OneOfBase<
ServiceResponse.OkResult,
ServiceResponse.NotFoundResult,
ServiceResponse.ErrorResult>
{
public class OkResult : ServiceResponse
{
}
public class NotFoundResult : ServiceResponse
{
}
public class ErrorResult : ServiceResponse
{
public string Message { get; }
}
}
Usage A
ServiceResponse result = ...;
if (result is ServiceResponse.OkResult ok)
...;
else if(result is ServiceResponse.ErrorResult error)
...;
Usage B
ServiceResponse result = ...;
result.Match(
ok => ...,
notFound => ...,
error => ...);

You have enum with types there. Take it and change it into specific objects with same interface(base class)
public abstract class ServiceResponse
{
public ServiceResponse(ServiceResult result, string message = null)
{
Result = result;
Message = result == ServiceResult.Error ? message ?? throw new System.ArgumentNullException(nameof(message)) : message; //questionable logic
}
public string Message { get; }
public ServiceResult Result { get; }
}
public class OkServiceResponse : ServiceResponse
{
public OkServiceResponse():base(ServiceResult.Ok){}
}
public class NotFoundServiceResponse : ServiceResponse
{
public NotFoundServiceResponse(string message):base(ServiceResult.NotFound, message){}
}
public class ErrorServiceResponse : ServiceResponse
{
public ErrorServiceResponse(string message):base(ServiceResult.Error, message){}
}

Related

C# Optional<TObject> as a return type?

Often i have a method where i want to return the error if something goes wrong, and instead of returning null, I want something less prone to errors at runtime and more easy to consume. Is there anything already done in .Net or maybe a nuget package?
Maybe have a constructor with optional parameters or object initializer would be enough?
This would have been the first approach but then every new Dto has to either have these Error property or inherit from a base class.
if (condition)
{
return new MyDto(null, error);
}
return new MyDto(someVariable, null);
So I've made this class to use a return type:
public class Optional<TObject> where TObject : class
{
public Optional(TObject? value)
{
Value = value;
}
public Optional(String error)
{
Error = error;
}
public TObject? Value { get; }
public String Error { get;} = String.Empty;
public Boolean IsError => !String.IsNullOrEmpty(Error);
}
I return it in the method:
if (condition)
{
return new Optional(error);
}
return new Optional(new MyDto(someVariable));
And then consume it like this:
var result = await myService.GetSomethingAsync();
if(result.IsError)
{
await DisplayAlert("error", result.Error, "Ok");
}
else
{
await DoSomethingElse(result.Value);
}
By creating a small class hierarchy, you could ensure that the Value property is only available when no error occurred
public abstract class Result
{
public virtual string Message => null;
public static Error Error(string message) => new Error(message);
public static Okay<T> Okay<T>(T value) where T : class => new Okay<T>(value);
}
public class Error : Result
{
public Error(string errorMessage) => Message = errorMessage;
override public string Message { get; }
}
public class Okay<T> : Result
where T : class
{
public Okay(T value) => Value = value;
public T Value { get; }
}
Usage
Result result = Result.Error("Something went wrong");
// OR
Result result = Result.Okay(new MyDto(someVariable));
if (result is Okay<MyDto> dtoResult) {
Console.WriteLine(dtoResult.Value);
} else {
Console.WriteLine(result.Message);
}
Or by using a recursive pattern, we can retrieve the value into a variable directly
if (result is Okay<MyDto> { Value: var dto }) {
Console.WriteLine(dto);
} else {
Console.WriteLine(result.Message);
}
Note that I have declared the Message property in the abstract base class Result, so that you don't have to cast to the Error type to get the message.
I used null as defualt value for the error message, as it allows us to write
Console.Writeline(result.Message ?? "okay");
This OneOf recommendation you got looks promising. I will personally have a look at it later.
What I do with my services is to standardize the result they return by using a SvcResult class or an inherited class.
Example:
public class SvcResult
{
public List<Error> Errors { get; } // Error is a class of my own. Add set; if deserialization is needed.
public bool Success { get; } // Add set; if deserialization is needed.
// Then parameterless constructor for a successful result.
// Then parameterized constructor to receive errors for a failed result.
}
That is the class for side-effect service calling. If The service returns data, I derive from the above to create DataSvcResult:
public class DataSvcResult<TResult> : SvcResult
{
public TResult Data { get; }
// Add constructor that receives TResult for a successful object result.
// Expose base class constructor that takes errors.
}
Basically that's what I do. But that OneOf thing, though. Looks super intersting.

Generic CQRS Query handler with custom return type

I am trying to build a generic query handler using the MediatR (v8) library. Lets jump to the code:
First of all I have an abstract query class like this:
public abstract class Query<TQueryResult> : IRequest<TQueryResult>
{
public Guid Id { get; } = Guid.NewGuid();
public DateTime Timestamp { get; }
protected Query()
{
Timestamp = DateTime.Now;
}
}
From the corresponding query handler I would like to return a Result wrapper object, which looks as the following:
public class Result<T>
{
public T Payload { get; }
public string FailureReason { get; }
public bool IsSuccess => FailureReason == null;
public Result(T payload)
{
Payload = payload;
}
public Result(string failureReason)
{
FailureReason = failureReason;
}
public static Result<T> Success(T payload)
=> new Result<T>(payload);
public static Result<T> Failure(string reason)
=> new Result<T>(reason);
public static implicit operator bool(Result<T> result) => result.IsSuccess;
}
And last but not least, lets see the query handler:
public abstract class AbstractQueryHandler<TQuery, TQueryResult, TResultValue> : IRequestHandler<TQuery, TQueryResult>
where TQuery : Query<TQueryResult>
where TQueryResult : class
{
public Task<TQueryResult> Handle(TQuery request, CancellationToken cancellationToken)
{
try
{
return HandleQuery(request);
}
catch (Exception e)
{
return Task.FromResult(Result<TResultValue>.Failure(GetFailureMessage(e)) as TQueryResult);
}
}
public abstract Task<TQueryResult> HandleQuery(TQuery request);
private static string GetFailureMessage(Exception e)
{
return "There was an error while executing query: \r\n" + e.Message;
}
}
To be honest I am not pleased with this solution due to the three type parameters I have in the query handler. Let's see some corresponding tests to reveal my concerns regarding. First the test-helper objects:
public class ExampleDto
{
public string Name { get; set; }
}
public class BasicQuery : Query<Result<ExampleDto>>
{
}
public class BasicQueryHandler : AbstractQueryHandler<BasicQuery, Result<ExampleDto>, ExampleDto>
{
public override Task<Result<ExampleDto>> HandleQuery(BasicQuery request)
{
return Task.FromResult(Result<ExampleDto>.Success(new ExampleDto() { Name = "Result Name" }));
}
}
And then the test:
[Fact]
public async Task GivenBasicQuery_whenHandle_thenSuccessResultWithPayload()
{
var handler = new BasicQueryHandler();
var result = await handler.Handle(new BasicQuery(), CancellationToken.None);
Check.That(result.IsSuccess).IsTrue();
Check.That(result.Payload.Name).IsEqualToValue("Result Name");
}
As you can see in the BasicQueryHandler there is some kind of duplication when declaring the three types, namely <BasicQuery, Result<ExampleDto>, ExampleDto>. It seems really fishy to me. I also tried many other possibilities, checking articles and SO questions/answers on the internet but could not come up with a cleaner solution with. What am I doing wrong? Is it possible to reduce the number of type parameters (of query handler) to 2? Thanks in advance for you help!
Basically, I moved the Result<> from the type parameter in the class declaration to the method declaration. I also removed the interfaces for clarity (you didn't share the definitions anyway).
public abstract class AbstractQueryHandler<TQuery, TQueryResult>
where TQuery : Query<TQueryResult>
where TQueryResult : class, new()
{
public Task<Result<TQueryResult>> Handle(TQuery request, CancellationToken cancellationToken)
{
try
{
return HandleQuery(request);
}
catch (Exception e)
{
return Task.FromResult(Result<TQueryResult>.Failure(new TQueryResult(), GetFailureMessage(e)));
}
}
public abstract Task<Result<TQueryResult>> HandleQuery(TQuery request);
private static string GetFailureMessage(Exception e)
{
return "There was an error while executing query: \r\n" + e.Message;
}
}
public class BasicQueryHandler : AbstractQueryHandler<BasicQuery, ExampleDto>
{
public override Task<Result<ExampleDto>> HandleQuery(BasicQuery request)
{
return Task.FromResult(Result<ExampleDto>.Success(new ExampleDto() { Name = "Result Name" }));
}
}
Note that since you need to new up a TQueryResult in the exception path, the new() keyword had to be added to the generic constraint.

How to correctly use generics and type constraints in this scenario?

I am confused on generics and type constraints, I will get straight to the point.
I have a BaseQueryResult class
public abstract class BaseQueryResult<T>
{
public int Count => Models != null && Models.Any() ? Models.Count : 0;
public Exception Exception { get; set; }
public bool HasException => Exception != null;
public bool IsSuccess => Exception == null;
public bool NotFound { get; set; }
public string ContinuationToken { get; set; }
public IList<T> Models { get; set; }
}
A child class which inherits from the above
public class TransportListingQueryResult : BaseQueryResult<TransportListingQueryModel>
{
public TransportListingQueryResult(IList<TransportListingQueryModel> models, string continuationToken)
{
Models = models;
ContinuationToken = continuationToken;
NotFound = false;
}
public TransportListingQueryResult(Exception exception)
{
NotFound = false;
Exception = exception;
}
public TransportListingQueryResult(bool notFound, Exception exception)
{
NotFound = notFound;
Exception = exception;
}
public static TransportListingQueryResult NotFoundResult(Exception exception)
{
return new TransportListingQueryResult(true, exception);
}
}
My Extension method that I am using
public static class TransportListingQueryResultExtension
{
public static IActionResult ToActionResult<T>(this T result, ControllerBase controller, int limit, string routeName, object values, HttpStatusCode successStatusCode = HttpStatusCode.OK)
where T : BaseQueryResult<T>
{
if (result.NotFound)
{
return controller.NotFound();
}
if (!result.IsSuccess)
{
if (result.HasException)
{
throw result.Exception;
}
return controller.BadRequest(new ErrorResponse { Messages = new[] { ErrorMessages.InternalServer } });
}
var uri = controller.Url.Link(routeName, values);
var response = new HyperMediaResponse<T>(
new LinkItem(uri),
new PageItem(limit, result.ContinuationToken, result.Count),
result.Models);
switch (successStatusCode)
{
case HttpStatusCode.Created:
return controller.Created(string.Empty, response);
case HttpStatusCode.OK:
default:
return controller.Ok(response);
}
}
}
And Finally my Action in my Controller
public async Task<IActionResult> Create([FromBody]CreateListingModel createListing)
{
var result = await _transportListingStore.CreateNewAsync(createListing);
return result.ToActionResult<TransportListingQueryResult>(this, 1, Constants.RouteNames.CreateListing, null, HttpStatusCode.Created);
}
I am getting an error on this line in my action
return result.ToActionResult
(
this,
1,
Constants.RouteNames.CreateListing,
null,
HttpStatusCode.Created
);
The error is:
The type 'TransportListings.API.Application.Response.TransportListingQueryResult' cannot be used as type parameter 'T' in the generic type or method 'TransportListingQueryResultExtension.ToActionResult(T, ControllerBase, int, string, object, HttpStatusCode)'. There is no implicit reference conversion from 'TransportListings.API.Application.Response.TransportListingQueryResult' to 'TransportListings.API.Application.Response.BaseQueryResult'
I am confused as my child class inherits BaseQueryResult, but the error is telling me there is no implicit reference conversion. I am not too sure what is wrong with my code and why its giving me the error it is.
Any help would be appreciated. Thanks in advance.
The problem is your constraint is impossible to satisfy:
public static IActionResult ToActionResult<T>(this T result, /* snip */)
where T : BaseQueryResult<T>
Where what you really want is something like:
public static IActionResult ToActionResult<T, U>(this T result, /* snip */)
where T : BaseQueryResult<U>
And call it like this:
return result.ToActionResult<TransportListingQueryResult, TransportListingQueryModel>(
this, /* snip */);

How should I return a generic response and response code from all functions in a .NET MVC app?

I want to be able to return a generic response from function calls in the business layer of my MVC application. Most of the time I see an object create function look like this
public int Create(ICNUser item)
{
return this._repository.Create(item);
}
public void Update(ICNUser item)
{
this._repository.Create(item);
}
In this case the _repository is a repository that wraps entity framework.
This works great for a lot of cases but I want more information to be returned and I want to have a success/failure variable and a response code for why this action failed validation. I want to optionally be able to return the inserted object or a selected object.
An example would be a create user function that returns an email can't be blank error and or a user already exists error and based on the error I show the user a different message.
The problem I'm running into is I want to have unit tests cover all of the possible response codes from a function without me having to go look at the code and try to figure out what the possible return values can be. What I'm doing feels like an anti-pattern. Is there a better way to accomplish all of this?
This is what I have now.
public IGenericActionResponse<ICNUser> Create(ICNUser item)
{
return this._repository.Create(item);
}
public IGenericActionResponse Update(ICNUser item)
{
return this._repository.Update(item);
}
Interfaces
namespace Web.ActionResponses
{
public enum ActionResponseCode
{
Success,
RecordNotFound,
InvalidCreateHash,
ExpiredCreateHash,
ExpiredModifyHash,
UnableToCreateRecord,
UnableToUpdateRecord,
UnableToSoftDeleteRecord,
UnableToHardDeleteRecord,
UserAlreadyExists,
EmailCannotBeBlank,
PasswordCannotBeBlank,
PasswordResetHashExpired,
AccountNotActivated,
InvalidEmail,
InvalidPassword,
InvalidPageAction
}
public interface IGenericActionResponse
{
bool RequestSuccessful { get; }
ActionResponseCode ResponseCode { get; }
}
public interface IGenericActionResponse<T>
{
bool RequestSuccessful { get; }
bool RecordIsNull{get;}
ActionResponseCode ResponseCode { get; }
}
}
implementations
namespace Web.ActionResponses
{
public class GenericActionResponse<T> : IGenericActionResponse<T>
{
private bool _requestSuccessful;
private ActionResponseCode _actionResponseCode;
public T Item { get; set; }
public GenericActionResponse(bool success, ActionResponseCode actionResponseCode, T item)
{
this._requestSuccessful = success;
this._actionResponseCode = actionResponseCode;
this.Item = item;
}
public GenericActionResponse(bool success, ActionResponseCode actionResponseCode)
{
this._requestSuccessful = success;
this._actionResponseCode = actionResponseCode;
this.Item = default(T);
}
public bool RecordIsNull
{
get
{
return this.Item == null;
}
}
public bool RequestSuccessful
{
get
{
return this._requestSuccessful;
}
}
public ActionResponseCode ResponseCode
{
get
{
return this._actionResponseCode;
}
}
}
public class GenericActionResponse : IGenericActionResponse
{
private bool _requestSuccessful;
private ActionResponseCode _actionResponseCode;
public GenericActionResponse(bool success, ActionResponseCode actionResponseCode)
{
this._requestSuccessful = success;
this._actionResponseCode = actionResponseCode;
}
public bool RequestSuccessful
{
get
{
return this._requestSuccessful;
}
}
public ActionResponseCode ResponseCode
{
get
{
return this._actionResponseCode;
}
}
}}
MVC app
public ActionResult ValidateResetHash(string passwordResetHash)
{
IGenericActionResponse result = (IGenericActionResponse)this._userManager.IsValidPasswordResetHash(passwordResetHash);
if (result.RequestSuccessful)
{
Models.PasswordChangeModel model = new Models.PasswordChangeModel();
model.PasswordResetHash = passwordResetHash;
return View("~/Areas/Public/Views/ResetPassword/PasswordChangeForm.cshtml", model);
}
else
{
switch (result.ResponseCode)
{
case ActionResponseCode.RecordNotFound:
{
FermataFish.Models.GenericActionModel responseModel = new FermataFish.Models.GenericActionModel(true, "/Login", "Login", "You have submitted an invalid password reset link.", false);
return View("~/Views/Shared/GenericAction.cshtml", responseModel);
}
case ActionResponseCode.PasswordResetHashExpired:
{
FermataFish.Models.GenericActionModel responseModel = new FermataFish.Models.GenericActionModel(true, "/ResetPassword", "Reset Password", "You have submitted an expired password reset link. You must reset your password again to change it.", false);
return View("~/Views/Shared/GenericAction.cshtml", responseModel);
}
default:
{
FermataFish.Models.GenericActionModel responseModel = new FermataFish.Models.GenericActionModel(true, "/", "Home", "An unknown error has occured. The system administrator has been notified. Error code:" + Enum.GetName(typeof(ActionResponseCode), result.ResponseCode), false);
return View("~/Views/Shared/GenericAction.cshtml", responseModel);
}
}
}
}
The switch statement in your ValidateResetHash response is a tad code smelly. This would suggest to me that you may benefit from the use of a subclassable enum. The subclassable enum would map action response codes or types to return views with models. Here is a compiling example of how to use this.
First some class fills I used to get a compiling example:
public class GenericActionModel
{
private bool v1;
private string v2;
private string v3;
private string v4;
private bool v5;
protected GenericActionModel() {}
public GenericActionModel(bool v1, string v2, string v3, string v4, bool v5)
{
this.v1 = v1;
this.v2 = v2;
this.v3 = v3;
this.v4 = v4;
this.v5 = v5;
}
}
public class ActionResult
{
private GenericActionModel responseModel;
private string v;
public ActionResult(string v, GenericActionModel responseModel)
{
this.v = v;
this.responseModel = responseModel;
}
}
public class PasswordChangeModel : GenericActionModel
{
public object PasswordResetHash
{
get;
set;
}
}
public interface IUserManager
{
Response IsValidPasswordResetHash(string passwordResetHash);
}
Next some infrastructure(framework) classes (I'm using StringEnum base class from the AtomicStack project for the ResponseEnum base class):
public abstract class Response
{
public abstract string name { get; }
}
public class Response<TResponse> : Response where TResponse : Response<TResponse>
{
private static string _name = typeof(TResponse).Name;
public override string name => _name;
}
// Base ResponseEnum class to be used by more specific enum sets
public abstract class ResponseEnum<TResponseEnum> : StringEnum<TResponseEnum>
where TResponseEnum : ResponseEnum<TResponseEnum>
{
protected ResponseEnum(string responseName) : base(responseName) {}
public abstract ActionResult GenerateView(Response response);
}
Here are some sample responses:
public class HashValidated : Response<HashValidated>
{
public string passwordResetHash;
}
public class InvalidHash : Response<InvalidHash> {}
public class PasswordResetHashExpired : Response<PasswordResetHashExpired> {}
public class Unexpected : Response<Unexpected> {}
A sample subclassable enum mapping the sample responses would look something like this:
public abstract class ValidateHashResponses : ResponseEnum<ValidateHashResponses>
{
public static readonly ValidateHashResponses HashOk = HashValidatedResponse.instance;
public static readonly ValidateHashResponses InvalidHash = InvalidHashResponse.instance;
public static readonly ValidateHashResponses PasswordResetHashExpired = PasswordResetHashExpiredResponse.instance;
public static readonly ValidateHashResponses Default = DefaultResponse.instance;
private ValidateHashResponses(string responseName) : base(responseName) {}
protected abstract class ValidateHashResponse<TValidateHashResponse, TResponse> : ValidateHashResponses
where TValidateHashResponse : ValidateHashResponse<TValidateHashResponse, TResponse>, new()
where TResponse : Response<TResponse>
{
public static TValidateHashResponse instance = new TValidateHashResponse();
private static string name = Response<TResponse>.Name;
protected ValidateHashResponse() : base(name) {}
}
protected class HashValidatedResponse : ValidateHashResponse<HashValidatedResponse, HashValidated>
{
public override ActionResult GenerateView(Response response)
{
PasswordChangeModel model = new PasswordChangeModel();
model.PasswordResetHash = ((HashValidated) response).passwordResetHash;
return new ActionResult("~/Areas/Public/Views/ResetPassword/PasswordChangeForm.cshtml", model);
}
}
protected class InvalidHashResponse : ValidateHashResponse<InvalidHashResponse, InvalidHash>
{
public override ActionResult GenerateView(Response response)
{
GenericActionModel responseModel = new GenericActionModel(true, "/Login", "Login", "You have submitted an invalid password reset link.", false);
return new ActionResult("~/Views/Shared/GenericAction.cshtml", responseModel);
}
}
protected class PasswordResetHashExpiredResponse : ValidateHashResponse<PasswordResetHashExpiredResponse, PasswordResetHashExpired>
{
public override ActionResult GenerateView(Response response)
{
GenericActionModel responseModel = new GenericActionModel(true, "/ResetPassword", "Reset Password", "You have submitted an expired password reset link. You must reset your password again to change it.", false);
return new ActionResult("~/Views/Shared/GenericAction.cshtml", responseModel);
}
}
protected class DefaultResponse : ValidateHashResponses
{
public static DefaultResponse instance = new DefaultResponse();
private DefaultResponse() : base("Default") {}
public override ActionResult GenerateView(Response response)
{
GenericActionModel responseModel = new GenericActionModel(true, "/", "Home", "An unknown error has occured. The system administrator has been notified. Error code:" + response.name, false);
return new ActionResult("~/Views/Shared/GenericAction.cshtml", responseModel);
}
}
}
Implementing the SampleController:
public class SampleController
{
private IUserManager _userManager;
public ActionResult ValidateResetHash(string passwordResetHash)
{
Response result = this._userManager.IsValidPasswordResetHash(passwordResetHash);
var resultType = ValidateHashResponses.TrySelect(result.name,ValidateHashResponses.Default);
return resultType.GenerateView(result);
}
}
Tweak the code above to fit your situation.
If you want to allow others to extend the ValidateHashResponses enum, you can make the constructor protected instead of private. They can then extend ValidateHashResponses and add their own additional enum values.
The point of using the subclassable enum, it to take adavantage of the TrySelect method that resolves responses to a specific enum value. Then we call the GenerateView method on the enum value to generate a view.
Another benefit of the enum is that if you need to have other decisions made based on the enum value, you simply add another abstract method to the enum and all value definitions will be forced to implement the new abstract method, unlike traditional enum/switch statement combinations where new enum values are not required to have cases added and where one may forget to revisit all of the switch statements where the enum was used.
DISCLAIMER:
I'm am the author of the AtomicStack project. Feel free to take the Subclassable enum class code from the project if you feel it would suit your needs.
UPDATE:
If you want to inject the response enum, you should create an IResponseHandler adapter interface with a GenerateViewForResponse type method and provide a concrete implementation that consumes the ValidateHashResponses enum.

C# add const field using attributes

We have class 'SomeClass':
namespace Namespace
{
class SomeClass
{
// something
}
}
And attribute 'SomeAttribute':
class SomeAttribute : System.Attribute { }
Task: add to all classes market by SomeAttribute 'public const string Type' field. Modified classes must be following:
class SomeClass
{
// something
public const string Type = #"Namespace.SomeClass";
}
UPD:
I'm using following approach for message transaction:
class Manager
{
// message has 3 parts:
// string message = String.Format("{0}{1}{2}",
// typeof(SomeClass).ToString(),
// splitter,
// Manager.Serialize(someClassObj)
// )
public static string GetType(string message) { /* some code */ }
public static string Serialize(SomeClass message) { /* XML serialization */ }
public static SomeClass Deserialize(string message) { /* deserialization */ }
}
class Logic
{
public void ProcessMessage(string message)
{
switch (Manager.GetType(message))
{
case SomeClass.Type:
{
SomeClass msg = Manager.Deserialize(message) as SomeClass;
// send message to binded objects
}
break;
case ClassInheritedFromSomeClass.Type:
{
// the same
}
break;
// etc.
}
}
}
UPD 2:
More about messages. At this time I'm using next approach:
public class BaseMessage
{
public const string Type = #"Messages.BaseMessage";
}
public class LoginMessage : BaseMessage
{
public new const string Type = #"Messages.Client.LoginMessage";
public string Nickname { get; set; }
public string Password { get; set; }
}
Conclusion
I think best case is to modify Manger like this:
class Manager
{
// create event table
public Action<BaseMessage> this[string eventName]
{
get
{
if (!m_eventTable.ContainsKey(eventName))
{
m_eventTable.Add(eventName, new Action<BaseMessage>(message => { }));
}
return m_eventTable[eventName];
}
set
{
m_eventTable[eventName] = value;
}
}
public void Send(BaseMessage message, string messageName)
{
if (m_eventTable.ContainsKey(messageName) && this[messageName].Method != null)
{
this[messageName].Invoke(message);
}
}
private Dictionary<string, Action<BaseMessage>> m_eventTable = new Dictionary<string, Action<BaseMessage>>();
}
Using switch with GetType is the wrong way to implement polymorphism, because it only checks the most-derived class (breaks extensibility).
In your particular case, where you want the Manager to be responsible for the behavior, you might use the dynamic keyword and overloaded methods. But this will again violate SOLID, because it isn't open for extension.
Instead of violating SOLID this way, try to find a way to use virtual methods to perform the type-specific action.

Categories