Reputation: 85
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
}
Upvotes: 4
Views: 219
Reputation: 735
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
}
Upvotes: 2
Reputation: 22849
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 => ...);
Upvotes: 1
Reputation: 780
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){}
}
Upvotes: 0
Reputation: 6032
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.
Upvotes: 1