简体   繁体   中英

Cast ISomeInterface<Nullable<bool>> to ISomeInterface<object>

I have a custom HtmlHelper where I am trying to get values of a generic type property. My ViewModel has properties of type ChangeRequestFormField. The relevant parts of my ViewModel, class/interface, and the html helper are shown below.

In my helper, I need to access the IsRequired and ValueHasChanged properties from my ViewModel properties. This is working fine for ChangeRequestFormField. But when I get to ChangeRequestFormField, I get a the following error:

Unable to cast object of type 'StaffChanges.Models.ChangeRequestFormField 1[System.Nullable 1[System.Boolean]]' to type 'StaffChanges.Models.IChangeRequestFormField`1[System.Object]'.

The error occurs at this line in the helper:

var isRequired = ((IChangeRequestFormField<object>)metadata.Model).IsRequired;

Maybe I'm approaching this wrong, but I need a way to access those properties in the helper, not knowing the type in the ChangeFormField until runtime.

ViewModel:

public class JobChangeModel
{
    public ChangeRequestFormField<string> Reason1 { get; set; }
    public ChangeRequestFormField<bool?> IsTransferEventNeeded { get; set; }
}


public class ChangeRequestFormField<T> : IChangeRequestFormField<T>
{
    public ChangeRequestFormField(string formFieldType, T fieldValue, T originalValue)
    {
        this.FieldValue = fieldValue;
        this.OriginalValue = originalValue;

        switch (formFieldType)
        {
            case FormFieldTypes.DoNotRender:
                this.RenderField = false;
                this.IsRequired = false;
                break;

            case FormFieldTypes.Required:
                this.RenderField = true;
                this.IsRequired = true;
                break;

            case FormFieldTypes.Optional:
                this.RenderField = true;
                this.IsRequired = false;
                break;

            default:
                this.RenderField = false;
                this.IsRequired = false;
                break;
        }
    }

    public T FieldValue { get; set; }

    public bool IsRequired { get; private set; }

    public T OriginalValue { get; set; }

    public string OriginalValueString
    {
        get
        {
            return this.OriginalValue == null ? string.Empty : this.OriginalValue.ToString();
        }
    }

    public bool ValueHasChanged
    {
        get
        {
            return !EqualityComparer<T>.Default.Equals(this.FieldValue, this.OriginalValue);
        }
    }
}


public interface IChangeRequestFormField<out T>
{
    bool IsRequired { get; }

    string OriginalValueString { get; }

    bool ValueHasChanged { get; }
}

public static MvcHtmlString LabelForChangeRequestFormField<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, string labelText, IDictionary<string, object> htmlAttributes)
{
    if (expression.Body.Type.GetGenericTypeDefinition() != typeof(ChangeRequestFormField<>))
    {
        return html.LabelFor(expression, htmlAttributes);
    }

    var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
    var isRequired = ((IChangeRequestFormField<object>)metadata.Model).IsRequired;
    var valueChanged = ((IChangeRequestFormField<object>)metadata.Model).ValueHasChanged;

    // other code left out
}

Based on your code it doesn't look like the IChangeRequestFormField interface needs to be generic. If you remove the type parameter T from the interface declaration you will be able to cast all derived generic classes to the non-generic interface.

public interface IChangeRequestFormField
{
    bool IsRequired { get; }
    string OriginalValueString { get; }
    bool ValueHasChanged { get; }
}

public class ChangeRequestFormField<T> : IChangeRequestFormField
{
    // ...
}

Then, you could use it like this:

var isRequired = ((IChangeRequestFormField)metadata.Model).IsRequired;

Things get more complicated if you need to generic types in the interface. Then you will need to be careful with how you implement the covariant or contravariant aspects of your interface to support your desired casting behavior. Take a look at this article on MSDN .

NOTE

In particular, the reason your covariant interface doesn't work is because there is a restriction that the covariant type must be a reference type. And since Nullable<T> is not a reference type the cast fails.

If you find that you really need the covariant behavior you could implement your own nullable reference type to wrap value types such as bool and int .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM