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...
- categorize your data
- map categories to binding symbols
- use binding symbols
- 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
Datetime.Adddays() Not Working as Expected
How to Pass a Lambda Expression to a Wcf Service
Auto-Resize Multiple Forms Rendered on Panel
Intersect with a Custom Iequalitycomparer Using Linq
Blocking Access to Private Member Variables? Force Use of Public Properties
How to Access Session in a Webmethod
Use Different Name for Serializing and Deserializing with JSON.Net
The JSON Value Could Not Be Converted to System.Int32
Use Local Images in Webbrowser Control
Why Does the Is Operator Return False When Given Null
Thread Safe Collections in .Net
How to Highlight Wrapped Text in a Control Using the Graphics
Read a Xml (From a String) and Get Some Fields - Problems Reading Xml