-
-
Notifications
You must be signed in to change notification settings - Fork 60
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[FEATURE] Support types with contravariance #70
Comments
In my project I have tones of cases where I need such a feature, because as @DreadKyller I need to build modular simulation. So I vote for this feature :) Though as I understand the case of example with actions and actors, next code explicitly declare that it needs at least [SerializeReference, SubclassSelector]
private List<IAction<INetworkActor>> actions; |
You are correct that this requests private void Example1(INetworkActor) {}
private void Example2(IActor actor) {}
---
Example1(new INetworkActor());
Example2(new INetworkActor()); In the end using contravariance means that it can accept T being any class that is of a less defined type. It basically specifies that a specific type will be provided as input. If I pass an // Only accepts IActor, INetworkActor and IStandardActor aren't valid since they are more-defined types
IAction<IActor>
// Only accepts IActor and INetworkActor as IStandardActor isn't part of INetworkActor's type hierarchy
IAction<INetworkActor>
// Only accepts IActor and IStandardActor as INetworkActor isn't part of IStandardActor's type hierarchy
IAction<IStandardActor> The opposite of this would be covariant, which you do by marking the parameter as For more information: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/covariance-contravariance/ Unity supports this just fine, adding an item to the list unity will serialize it properly and it works without issue, the problem comes from the selection. Do to an optimization that was made to For my purposes I only have a few types so I just modified the subclass selector to allow my specifying other types to search in addition to the one specified in the field type. Still I think that finding a solution to this would be very useful. |
Yep, seems that I misunderstood you. Fully agree. I have tons of cases where I need such a feature. |
If we can check that type is covariance/contravariance generic interface and get it's generic |
Indeed might be an option to look into. I don't know much about the internals of Unity's TypeCache, if we construct a Type based on the type of the field and parameters I'm not entirely certain whether it'll match the entry in the type cache. Also when dealing with multiple parameters this becomes more difficult, if you have 2 parameters with 3 options each that now becomes 9 combinations you have to check derived types from. |
I am currently trying an experimental type search. I may be missing a case, but so far it appears to be working. using System;
using System.Collections.Generic;
using UnityEngine;
public interface IActor { }
public interface IStandardActor : IActor { }
public interface INetworkActor : IActor { }
public interface IAction<in T> where T : IActor { }
public interface IActorAction : IAction<IActor> { }
public interface IStandardActorAction : IAction<IStandardActor> { }
public interface INetworkActorAction : IAction<INetworkActor> { }
[Serializable]
public sealed class StandardActorAction : IAction<IStandardActor> { }
[Serializable]
public sealed class ActorAction : IAction<IActor> { }
[Serializable]
public class BaseAction<T> : IAction<T> where T : IActor { }
[Serializable]
public sealed class DerivedAction1 : BaseAction<IActor> { }
[Serializable]
public sealed class DerivedAction2 : BaseAction<INetworkActor> { }
[Serializable]
public sealed class DerivedAction3 : BaseAction<IStandardActor> { }
[Serializable]
public sealed class NetworkActorAction1 : INetworkActorAction { }
[Serializable]
public sealed class NetworkActorAction2 : IAction<INetworkActor> { }
[Serializable]
public sealed class NetworkActorAction3 : IAction<IActor> { }
public class Example_Contravariance : MonoBehaviour
{
[SerializeReference, SubclassSelector]
public List<IAction<INetworkActor>> action = new List<IAction<INetworkActor>>();
} public static IEnumerable<Type> GetTypes (Type baseType)
{
var result = new List<Type>();
Type genericInterfaceDefinition = null;
Type[] targetTypeArguments = null;
Type[] genericTypeParameters = null;
if (baseType.IsGenericType)
{
genericInterfaceDefinition = baseType.GetGenericTypeDefinition();
targetTypeArguments = baseType.GetGenericArguments();
genericTypeParameters = genericInterfaceDefinition.GetGenericArguments();
}
else
{
genericInterfaceDefinition = baseType;
targetTypeArguments = Type.EmptyTypes;
genericTypeParameters = Type.EmptyTypes;
}
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (var assembly in assemblies)
{
foreach (var type in assembly.GetTypes())
{
if ((type.IsPublic || type.IsNestedPublic || type.IsNestedPrivate) &&
!type.IsAbstract &&
!type.IsGenericType &&
!typeof(UnityEngine.Object).IsAssignableFrom(type) &&
Attribute.IsDefined(type, typeof(SerializableAttribute)) &&
!Attribute.IsDefined(type, typeof(HideInTypeMenuAttribute)))
{
var interfaces = type.GetInterfaces();
foreach (var iface in interfaces)
{
if (iface.IsGenericType && iface.GetGenericTypeDefinition() == genericInterfaceDefinition)
{
var sourceTypeArguments = iface.GetGenericArguments();
bool allParametersMatch = true;
for (int i = 0; i < genericTypeParameters.Length; i++)
{
var variance = genericTypeParameters[i].GenericParameterAttributes & GenericParameterAttributes.VarianceMask;
var sourceTypeArg = sourceTypeArguments[i];
var targetTypeArg = targetTypeArguments[i];
if (variance == GenericParameterAttributes.Contravariant)
{
if (!sourceTypeArg.IsAssignableFrom(targetTypeArg))
{
allParametersMatch = false;
break;
}
}
else if (variance == GenericParameterAttributes.Covariant)
{
if (!targetTypeArg.IsAssignableFrom(sourceTypeArg))
{
allParametersMatch = false;
break;
}
}
else
{
if (sourceTypeArg != targetTypeArg)
{
allParametersMatch = false;
break;
}
}
}
if (allParametersMatch)
{
result.Add(type);
break;
}
}
}
}
}
}
return result;
} |
If released, |
@DreadKyller |
Feature destription
Given a structure like the following:
Where
IStandardActor
andINetworkActor
have a shared super interface ofIActor
.We can imagine the situation where
IActor
is used for actions that are compatible with both standard and networked actors, whereIStandardAction
andINetworkAction
are only compatible withIStandardActor
andINetworkActor
respectively, whereIStandardActor
may be implemented on aMonoBehaviour
andINetworkActor
may be implemented on aNetworkBehaviour
.Under such conditions, attempting to store the list of actions as such:
That list of actions should allow selection of both
IAction<INetworkActor>
andIAction<IActor>
, asIAction<IActor>
is contravariant toIAction<INetworkActor>
andIAction::T
is marked as supporting contravariance. In fact if I manually add an implementation of both to the list it is fully supported, but onlyIAction<INetworkActor>
specifically are found with the subclass selector.This scenario is what we find ourselves dealing with when working on a modular controller for our games. Supporting contravariant types like this would, I feel, be quite useful.
The text was updated successfully, but these errors were encountered: