简体   繁体   中英

Checking Whether Class Implements Generic Interface

I'm using the following generic function to determine whether a class implements a specified interface:

private static bool HasFieldType<TEntity, TInterface>()
{
    return typeof(TInterface).IsAssignableFrom(typeof(TEntity));
}

This works fine for the majority of the time.

However, I now have an interface which has a generic parameter:

public interface IStatusField<TEnum> where TEnum : System.Enum
{
    TEnum Status { get; set; }
}

And this causes the HasFieldType function to break with an unexpected use of unbound generic name error.

Ideally, I want to call the function like:

if (HasFieldType<TEntity, IStatusField<>>()) 
{
    // builder is an EntityTypeBuilder instance
    builder.Property("Status")
        .HasMaxLength(255)
        .HasConversion(new EnumToStringConverter<>());
}

But this won't work as I'm not specifying the generic type for both the IStatusField<> or the EnumToStringConverter<> .

Is there any way around this?

UPDATE

This code forms part of a generic base IEntityTypeConfiguration class as follows:

public abstract class EntityTypeConfiguration<TPrimaryKey, TEntity> : IEntityTypeConfiguration<TEntity> where TEntity : Entity<TPrimaryKey>
{
    public void Configure(EntityTypeBuilder<TEntity> builder)
    {
        builder.HasKey(e => e.Id);

        builder.Property(e => e.Id)
            .IsRequired()
            .HasMaxLength(13)
            .HasValueGenerator<PrimaryKeyValueGenerator>();

        // Apply the generic interface properties
        builder.ApplyInterfaceFields<TPrimaryKey, TEntity>();

        // Apply any additional configuration
        this.OnConfigure(builder);
    }

    protected abstract void OnConfigure(EntityTypeBuilder<TEntity> builder);
}

// In an extension class, I have
public static void ApplyInterfaceFields<TPrimaryKey, TEntity>(this EntityTypeBuilder<TEntity> builder) where TEntity : Entity<TPrimaryKey>
{
    // Check other implementations (removed for brevity)

    // IStatusField implementation
    if (HasFieldType<TEntity, IStatusField<>())
    {
        builder.Property("Status")
            .HasMaxLength(255)
            .HasConversion(new EnumToStringConverter<>());
    }

}

At the point of checking for IStatusField implementation, I know nothing about the generic type specified. I think this may be the bigger problem...

Ok, so I've managed to punch my way around the problem.

It needs a bit of tidying and some error checking but in the crudest form:

private static bool HasFieldType<TEntity>(Type interfaceType)
{
    var interfaces = typeof(TEntity).GetTypeInfo().ImplementedInterfaces;

    // Iterate through the interfaces
    foreach (var intf in interfaces)
    {
        // Compare interface names
        if (intf.FullName.Contains(interfaceType.FullName))
        {
            return intf.IsAssignableFrom(typeof(TEntity));
        }
    }

    return false;
}

Which now enables this to work:

// IStatusField implementation
if (HasFieldType<TEntity>(typeof(IStatusField<>)))
{
    builder.Property("Status")
        .HasMaxLength(255)
        .HasConversion<string>();
}

I can just use the built-in automatic string-to-enum conversions from EF to do the grunt work.

Instead of trying to wrestle with resolving generic type arguments from nothing, you might consider approaching it from the opposite direction, by getting a list of interfaces implemented by TEntity , filtering it to search for an IStatusField . Once you've located the field, you can get its' generic type arguments and pass those to your EnumToStringConverter :

var statusField = typeof(TEntity)
    .GetInterfaces()
    .FirstOrDefault(x => x.Name.StartsWith("IStatusField"));

Value given TEntity : IStatusField<ConsoleColor> :

statusField.GenericTypeArguments = [ typeof(System.Color) ]

From there though you're not done; you must still construct an instance of the generic type EnumToStringConverter<System.Color> . This is rather simple and outlined here .

Edit: I realized that because you'd be invoking a constructor, it's not quite the same. Here's how you'd accomplish this:

var statusField = typeof(TEntity)
            .GetInterfaces()
            .FirstOrDefault(x => x.Name.StartsWith("IStatusField"));

        if (statusField != null)
        {

            var enumType = statusField.GenericTypeArguments[0]; // get the IStatusField<T> value

            // get the default constructor after supplying generic type arg
            var converterType = typeof(EnumToStringConverter<>)
                .MakeGenericType(enumType)
                .GetConstructors()[0];

            // invoke the constructor. Note the null (optional) param
            dynamic converter = converterType.Invoke(new Object[1]);

                builder.Property("Status")
                    .HasMaxLength(255)
                    .HasConversion(converter);
        }

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