Use the Dynamic Keyword/.Net 4.6 Feature in Unity

Use the dynamic keyword/.NET 4.6 feature in Unity

The first step is to check if Unity recognizes these 2 basic C# 6 features from MS site.

1.Try "Index Initializers" feature:

private Dictionary<int, string> webErrors = new Dictionary<int, string>
{
[404] = "Page not Found",
[302] = "Page moved, but left a forwarding address.",
[500] = "The web server can't come out to play today."
};

2. then "String Interpolation" feature:

private string FirstName = "";
private string LastName = "";
public string FullName => $"{FirstName} {LastName}";

If they give you error then the problem is not just the dynamic keyword but a problem that Visual Studio cannot recognize the .NET version being set by Unity.

From the comment section your Unity failed to compile the first example.


Go through the steps one by one for a possible fix. Do not skip of them.

1.Go to Edit --> Project Settings --> Player --> Other Settings --> Configuration --> Scripting Runtime Version --> Experimental (.Net 4.6 Equivalent).

2.Go to Edit --> Project Settings --> Player --> Other Settings --> Configuration --> Api Compatibility Level --> .NET 4.6

3.Restart Unity Editor and Visual Studio. You must restart both.

Test both C# features above. If they work then the dynamic keyword should as-well. If they don't then move on to #4.

4.Update Visual Studio. This is very important. Update the visual Studio to the latest version/patch.

5.If you can't still get both C#6 features above to compile then Re-install both Visual Studio and Unity then perform step #1 and #2 again as some files are missing.

6.Finally, if you get both C#6 features working but the dynamic keyword is still not working then update from Unity 2017.1 to Unity 2017.2. This version fixed many .NET issues.

Note that I am using Unity 2017.2 with the dynamic keyword without any issue. Also, GraphQL is working fine.

Range attribute using dynamic values instead of fixed

Just add a constructor:

private double _MinValue, _MaxValue; // no readonly keyword

public ValidateCustomAttribute(double min, double max, Func<string> errorMessageAccessor)
: base(errorMessageAccessor)
{
_MinValue = min;
_MaxValue = max;
}

What you can't do is have variables in the attribute constructor invokation.

This is not possible:

[ValidateCustom(min, max)]

But if you use literals (or constants) in your code you can have these:

[ValidateCustom(1, 1000)]

And on another class or method:

[ValidateCustom(3, 45)]

What you are missing is the constructor taking in those static values and affixing them to the construct you are describing with your attribute.


EDIT: The ugly way around this

If you really really need this, you can circumvent the limitation but it is as ugly as it can get. I am STRONGLY against this, but you asked for it...

  1. categorize your data
  2. map categories to binding symbols
  3. use binding symbols
  4. resolve binding symbols to data

So, let's get to work:

1) categorize your data

Say, your data is a range (min, max), the first thing to do is establish which values are possible, let's say you have 4 possible ranges (may be hundreds, but that's anther problem altogether).

(1, 1000)
(10, 20)
(3, 45)
(5, 7)

2) map categories to binding symbols

Now you have to use an enum as binding symbols for those ranges:

public enum MyRanges
{
R1, R2, R3, R4
}

3) use binding symbols

Define the constructor as taking in the binding symbol:

private MyRanges _R;

public ValidateCustomAttribute(MyRanges r, Func<string> errorMessageAccessor)
: base(errorMessageAccessor)
{
_R = r;
}

The attribute will be used like this:

[ValidateCustom(MyRanges.R2, "ERROR!")]

4) resolve binding symbols to data

The last you need is a dictionary with the actual data:

Dictionary<MyRanges, double> dataMin = {
{ MyRanges.R1, 1},
{ MyRanges.R2, 10},
{ MyRanges.R3, 3},
{ MyRanges.R4, 5}
};

Dictionary<MyRanges, double> dataMax = {
{ MyRanges.R1, 1000},
{ MyRanges.R2, 20},
{ MyRanges.R3, 45},
{ MyRanges.R4, 7}
};

The test will use the binding this way:

public override bool IsValid(object value)
{
double val = (double)value;
return val >= dataMin[_R] && val <= dataMax[_R]; // get data through binding
}

Now you can change behind the scenes those values and all attributes bound by the binding symbols behave differently:

dataMax[MyRanges.R4] = 29;

Done.

What cannot change is now the binding from attribute to category, but the data contained in the category is free to change.

But ugly and impossible to maintain. Don't do it, really.

C# dynamic compilation and Microsoft.CSharp.dll error

Ok. Basically, my mistake was linked to the fact that I added my IronPython assemblies from the wrong platform.
Verify that:

  • Target Framework: 4.0

  • Add all the assemblies provided by IronPython in [IronPython-2.7.3]->[Platforms]->[Net40].

Thx to everyone who gave me advices.

Ps:Now, of course, there is another problem… But it's not about that topic anymore.

Access to the Coroutine through an unassigned(unknown) component

You can't do this without reflection which is slow depending on how often this is done.

To simplify your code, you have to use Dictionary or provide a way to translate the _ID to your function. Since you're yielding each coroutine function call, you have to store each function as IEnumerator so that you can yield it.

The Dictionary:

Dictionary<int, IEnumerator> idToDict = new Dictionary<int, IEnumerator>();

Function to add the IDs and it's functions to the Dictionary. Call this function from the Awake or Start function.

void InitIDs()
{
idToDict.Add(1, Elements[5].GetComponent<ID1>().StartAttack(EnemysInBattle, HeroesInBattle, Attacker));
idToDict.Add(2, Elements[5].GetComponent<ID2>().StartAttack(EnemysInBattle, HeroesInBattle, Attacker));
idToDict.Add(3, Elements[5].GetComponent<ID3>().StartAttack(EnemysInBattle, HeroesInBattle, Attacker));
idToDict.Add(4, Elements[5].GetComponent<ID4>().StartAttack(EnemysInBattle, HeroesInBattle, Attacker));
idToDict.Add(5, Elements[5].GetComponent<ID5>().StartAttack(EnemysInBattle, HeroesInBattle, Attacker));
}

To use it, check for the _ID value in the Dictionary. If it exist, execute the coroutine function paired with it then yield each one just like you did in your original code:

int _ID = Attacker.GetComponent<BaseHeroStats>().ID_Model;

IEnumerator action;
//Check if the function name exist, start it then yield it
if (idToDict.TryGetValue(_ID, out action))
{
//Execute the approprite code
yield return StartCoroutine(action);
}

EDIT:

Another option is to replace your _ID with string. That string should instead contain the name of the script. You can then use reflection and the dynamic keyword to call the coroutine function. So, int _ID should now be string _ID which contains the name of the script. This also means that the ID_Model variable in your BaseHeroStats class should now be a string.

For example something like this:

string _ID = "ID2";
Type type = Type.GetType(_ID);
Component ids = GetComponent(type);
dynamic val = Convert.ChangeType(ids, type);
StartCoroutine(val.StartAttack());

Or in your own code example:

string _ID = Attacker.GetComponent<BaseHeroStats>().ID_Model;

Type type = Type.GetType(_ID);
Component ids = Elements[5].GetComponent(type);
dynamic val = Convert.ChangeType(ids, type);
yield return StartCoroutine(val.StartAttack(EnemysInBattle, HeroesInBattle, Attacker));

You must enable the .NET 4.6 to use the dynamic keyword. See this post. This should work but use the Dictionary version of this code because it's faster.

How to use Html.MvcSiteMap().Menu() with dynamic parameter?

There are 2 ways, and it depends on whether the route values are directly used to identify page, or are some ambient value.

For values that directly identify the page, you should configure the nodes using IDynamicNodeProvider or ISiteMapNodeProvider so they can build new nodes when new records are added to the database.

public class ProductDynamicNodeProvider 
: DynamicNodeProviderBase
{
public override IEnumerable<DynamicNode> GetDynamicNodeCollection(ISiteMapNode node)
{
// TODO: Replace MyEntities with your entity framework context object
using (var db = new MyEntities())
{
// Create a node for each album
foreach (var album in db.Products)
{
DynamicNode dynamicNode = new DynamicNode();
dynamicNode.Title = product.Name;
dynamicNode.ParentKey = "Products"; // There must be a node in the SiteMap with key set to "Products" for this to work

dynamicNode.PreservedRouteParameters.Add("id"); // Force a match on this parameter always
dynamicNode.RouteValues.Add("productId", product.Id);

yield return dynamicNode;
}
}
}
}

And for dynamic node providers, you need a definition node that will be used as a template when creating the nodes, and it will not actually exist in the SiteMap.

// This node will be added to the SiteMap and serves as the parent node of each product
<mvcSiteMapNode title="All Products" controller="Products" action="Index" area="" key="Products">
// This node will become a template for each product node, but the node itself won't be added to the SiteMap
<mvcSiteMapNode controller="Products" action="Details" area="" dynamicNodeProvider="MyNamespace.ProductDynamicNodeProvider, MyAssemblyName" />
</mvcSiteMapNode>

For ambient values (userId, sessionId, etc.), you can force the SiteMap to always match them by configuring them as preservedRouteParameters.

<mvcSiteMapNode title="Sub2" controller="SitemapTest" action="Sub2" area="" preservedRouteParameters="clientId,productId,staffId,id" />

You can also combine these 2 techniques by forcing a match on certain parameters and providing node instances for combinations of other parameters.

For an in depth look at these options, read How to Make MvcSiteMapProvider Remember a User's Position.



Related Topics



Leave a reply



Submit