C# Delegate value does not fall within the expected range error - c#

this is a question programming C# within Unity game engine, but it might be C# delegate expert without Unity experience could know what I'm doing wrong? (I did post on Unity forum already).
I am trying to pass an arbitrary function to be called inside a fixed function, which itself is called using delegate keyword through the AddListener function of InputField.onEndEdit event. I am new to delegates, but as I understand, I need to create a new delegate to do this, and I'm getting bit tangled up, having read a few delegate articles still stuck. Currently, code compiles but get ArgumentException error. Unfortunately, with full stack trace on in Unity Editor, this is all the stack trace I get:
ArgumentException: Value does not fall within the expected range.
CharacterController3D..ctor () (at Assets/Entities/DungeonCell/Priest/CharacterController3D.cs:95)
Where the script name is CharacterController3D.cs. Line 95 constructor in this class, generating this error is:
Del marySuccess = question.MarySuccess;
End Objective: When player inputs an answer to an arbitrary question posed when player triggers it in game, the ProcessAnswer function will call a customised function processing correct answer to each question, ie each question will have different function dealing with a correct answer response. Likewise, the ProcessAnswer function will call different custom function for that question if player gives wrong answer and so on.
Below is the ProcessAnswer function, with arbitrary delegate passed to it delegate1, which in following code block runs when correct answer is given:
private void ProcessAnswer (InputField input, string answer, float clockStart, Del delegate1)
{
if (String.Equals(answer, input.text.Trim(), StringComparison.OrdinalIgnoreCase)) // trim trailing spaces, accept any case in letters typed
{
countdownTimer.enabled = false; // stops timer
float timePoints = Mathf.Round(2000/(clockStart - countdownTimer.TimeStoppedTenthsSecond()));
delegate1();
}
else ....
Above function is called in same script by:
string answer = "grace";
clockStart = 90f;
inputField.onEndEdit.AddListener(delegate { ProcessAnswer(inputField, answer, clockStart, marySuccess); });
where marySuccess is the delegate parameter in this case, defined by:
private delegate void Del();
Del marySuccess = question.MarySuccess;
Still in this same script, question here is declared as a static instance of a separate component script on same object. I understood have to make it static for the delegate to reference it, didn't work as non-static anyway:
private static Questions question;
and initialised in the Awake method, all in same script:
question = this.GetComponent<Questions>();
EDIT
As comments, apology for missing the MarySuccess() method code itself, which has same signature as the delegate declaration (no parameters, returns void):
public void MarySuccess()
{
string phrase1 = "a";
string phrase2 = "b";
string phrase3 = "c";
string phrase4 = "d";
thoughtText.color = new Color(0.55f, 0f, 0.03f); // now matches (141,0,8) colour of input text, was Color.red;
thoughtText.fontSize = 22;
StartCoroutine(player.FillThoughtBox(phrase1, 3, phrase2, 3, phrase3, 5, phrase4));
playerInputPanel.SetActive(false);
player.Invoke("MaryHailed", 10f);
Invoke("HideThoughtPanel", 22f);
}
// use Invoke() with desired delay, so that player can start moving and have more time to read the words before it disappears
private void HideThoughtPanel()
{
thoughtButtonPanel.SetActive(false);
}
EDIT2
I checked even with public void MarySuccess () {} empty, still gives same exception error.
Any suggestions very grateful, thanks!

I fixed this long time ago now, but in case anyone else reads, the problem was using private scope for the delegate function. From above question, code I had was:
private delegate void Del();
Del marySuccess = question.MarySuccess;
which generated the error in 2nd line. It should be:
public delegate void Del();
because MarySuccess() method is public method, the delegate signature needs to match it, and also be public. Indeed, since this was calling the method in another class, the method called would have to be public to work, and therefore the delegate also needs to be public, otherwise the 'value' of marySuccess here does not 'fall within the expected range' ie a public delegate 'value'. I had thought that since Del() only used in this class, it could be private, but not appreciating that Del() is used as a data type, as well as a variable. It's a confusing error message I feel, since I feel that the delegate type is being assigned a reference not a value, but very welcome if someone more experienced can correct any errors in detail of this analysis..

I got the very same exception in a very similar case. In my case it turned out that the expression question.MarySuccess (fetching a delegate) raises this particularly unexpected error if question is null. That's the difference between
Del x = question.MarySuccess;
and
Del x = () => question.MarySuccess();
The second version will work even if question is null, and raise a NullReferenceException only later and only if x is actually called.

Related

C# Unity2D Is it possible to call a script based on a string? [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 2 years ago.
Improve this question
Is it possible to run a method in a script where the method's name is found from a text variable?
For example:
public text MyVariable;
public void Start()
{
MyVariable = RunMeBaby;
MyVariable();
}
public void RunMeBaby()
{
Debug.Log("You did it!");
}
In the above example I'm assigning the variable 'MyVariable' to 'RunMeBaby' (the name of the method I want to call) and then attempting to call it.
The above doesn't work for me, but is there another way?
EDIT TO ADD ADDITIONAL CLARIFICATION BELOW
I am currently creating my own card game. I've created a scriptable object (CardAsset) to handle some fields that all my populated cards will need. Stuff like:
Card Name (string)
Cost (int)
Image (image)
Description Text (string)
I'm now at the point all the other stuff for my prototype is complete (playing field, deck, etc), and I need to start actually making the effects for my cards. Since every card is different but some of the effects are the same (draw more cards and deal damage to a character are very common for instance), I figured I could alter my scriptable object CardAsset to include some scripts and variables:
Card Name (string)
Cost (int)
Image (image)
Description Text (string)
Effect1 (script)
Effect1Amount (int)
Effect2 (script)
Effect2Amount (int)
Effect3 (script)
Effect3Amount (int)
This way, I could create CardAssets with upto 3 effects on them (again such as dealing damage to a character or drawing cards) and then use something in my game to call those scripts with the associated variables when the player plays that card.
My issue is that I cannot attach scripts to my CardAsset, so I figured a suitable workaround would be for me to type in the name of a method (DealDamage, DrawCards) in the Effect1/Effect2/Effect3 slot on the CardAsset (changing them to a string), and then have something read them and call the associated method elsewhere.
I understand that this solution is prone to errors and could be painful to maintain/change, so is there a better way I could do it?
Hello and welcome to Stack Overflow,
I don't know the details about your text class, but from your question I assume it's a string container (so to say).
You can do exactly what you are asking (calling a method based on a string) using reflection:
// You need the class name, so I just called it MyClass
public class MyClass
{
public void Start()
{
string methodName = "RunMeBaby";
// Invoke the method using reflection
typeof(MyClass).GetMethod(methodName)?.Invoke(this, null);
}
...
However this is a VERY expensive (and error prone) call.
My suggestion would be to use a less versatile approach:
public void Start()
{
// assuming text.Text is your string property
string methodName = "RunMeBaby";
CallMethod(methodName);
}
private void CallMethod(string methodName)
{
switch(methodName)
{
case nameof(RunMeBaby):
RunMeBaby();
break;
case nameof(SomeOtherMethod):
SomeOtherMethod();
break;
}
}
private void RunMeBaby()
{
}
private void SomeOtherMethod()
{
}
NOTE:
I don't know where you get your method strings from, but if it is not from some outer source (e.g. user input), you are best advised to use the nameof keyword instead of hard coded strings. If possible you should get rid of the strings altogether and replace them with an enum representing the callable methods for example, but this solution depends on your usecase.
If you want to call methods by name. you can use reflection:
Test type = new Test();
typeof(Test).GetMethod("Hello").Invoke(type, new[] { "world" });
You can do so through reflection in C#. But before we get there, I would recommend
if possible you make use of Action Delegate (https://learn.microsoft.com/en-us/dotnet/api/system.action?view=netframework-4.7.2) to which you can assign your method and later call it wherever you want.
So, your code will look like:
public Action MyVariable =RunMeBaby;
MyVariable();
Now the reflection way:
public string MyVariable = "RunMeBaby";
public void Start()
{
this.GetType().GetMethod(MyVariable).Invoke(this, null);
}
Above code assumes that method to be called ("RunMeBaby" in your case) is public.
https://learn.microsoft.com/en-us/dotnet/api/system.type.getmethod?view=netframework-4.7.1
https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/reflection

Passing Actions into generic functions

I'm trying to wrap my head around different concepts in Csharp by trying different things. A create a generic function that takes in an action. The action has one input parameter and returns void. I create a simple action that is linked to a lambda function (returns void has one parameter x). I am able to run the action but when I pass the function to my generic function I am not sure how to add the input parameter. act("Some Int") doesn't work.
How do I pass in a value to an action?
public MainWindow()
{
InitializeComponent();
Action<int> myAction = (x) => Console.WriteLine(x);
myAction(13);
test(myAction);
}
private static void test<T>(Action<T> act)
{
act(); // How do i pass in an int Here?
}
Simply calling act("Some Int") as you have just required the Action act to be a genric function. Therefore you cannot specifically invoke it with one fixed variable type. You can solve your problem by modifying the test-method
private static void test<T>(Action<T> act, T value)
{
act(value); // How do i pass in an int Here?
}
...
test(myAction,integerValue);
Now you can call the Action with a given intvalue.
I can see what you are trying to do, and just wanted to throw this pattern up, since we often do this when we have to use closures and the parameters could be wildly different.
In those cases, rather than define an Action<T> which kind of ties you down from being able to use closures, you would just simply define your method as Action. So test would look like this:
private static void test(Action act)
{
act(); // yup, that's all there is to it!
}
So how would you pass in the parameter(s)? Simple: use closures. Like this:
public MainWindow()
{
InitializeComponent();
var x = 13; // this defined outside now...
Action myAction = () => Console.WriteLine(x); // you're basically using the closure here.
myAction();
test(myAction);
}
We often use this sort of approach when we're context switching (aka thread jumping), and need the thread continuation to pick up one or more variable values at the point it executes. That's just one example, there's quite a few other valid use cases as well.
Your experimental example, if I'm reading it correctly, could also qualify as a situation where closures could be a good fit.

Using variables from another script int Unity 5

I need to use some variables defined in a script but when I call them their values are 0. I don't know what I am doing wrong.
Example:
Script1.cs
public int cont;
public void Method() { cont++; }
void Update() { Method(); }
Script2.cs
public Script1 usingScript1;
void MethodX()
{
usingScript1.GetComponent<Script1>();
Debug.Log(usingScript1.cont);
}
void Update() { MethodX(); }
This script should be showing the "cont" variable increasing since it's being called from Update(), but that's not happening. When I call it, it's 0 and don't increase.
Also, I refer the object which contains Script1.cs in the Ispector. It must be a simple thing that I'm missing. I even tried calling Method().
Just to add to what everyone mentioned, have you tried initializing "cont" ?
This
public int cont;
becomes
public int cont = 0;
Also try initializing it in the Start() function if this doesn't work.
The function called Method() is never called anywhere in this code. Since it's the only thing that modifies the value of the variable called cont, if it is never called, cont will never change from its default value of zero.
EDIT: Whoops!
Okay, the actual problem here is that you need to change
usingScript1.GetComponent<Script1>();
to
usingScript1 = GetComponent<Script1>();
The latter line of code sets the variable usingScript1 so that you can use it in your code. The former simply calls a function without doing anything with the information it returns.
EDIT: GetComponent() will only work is the two scripts are attatched to the same gameobject. Otherwise, you can use FindObjectOfType().
I managed to solve it and never thought about it. As simple as it. Instad of creating an object, I just made the variables static and call 'em using the class.
Script1.cs
public static cont;
Script2.cs
Script1.cont;
And it works.

Using anonymous event in function

public static void OnAutoScrollToEndChanged(DependencyObject s, DependencyPropertyChangedEventArgs e)
{
/* ... */
var scrollToEndHandler = new NotifyCollectionChangedEventHandler((sender, args) => // 수정
{
if (listBox.Items.Count > 0)
{
object lastItem = listBox.Items[listBox.Items.Count - 1];
listBoxItems.MoveCurrentTo(lastItem);
listBox.ScrollIntoView(lastItem);
}
});
if (isAutoScroll)
{
source.CollectionChanged += scrollToEndHandler; // A
}
else
{
source.CollectionChanged -= scrollToEndHandler; //B
}
}
https://michlg.wordpress.com/2010/01/17/listbox-automatically-scroll-to-bottom/
This code is referenced by upper URL.
A(scrollToEndHandler) and B(scrollToEndHandler) are in a function.
When 'AutoScrollToEndProperty' is changed, 'OnAutoScrollToEndChanged' will be called all the time.
I wondering whether These are same reference. Thanks.
If your question is basically, "Does unsubscription actually work here?" the answer is C# compiler implementation-specific, theoretically.
On a practical basis, the body of the lambda expression doesn't capture any local variables, but does capture this (by referring to listBox)... so I'd expect the compiler to generate an instance method containing the body of the code in the lambda expression.
If the method is called multiple times on the same target (i.e. this refers to the same object), I'd expect scrollToEndHandler to be a distinct but equal delegate each time - in other words, it would create a new delegate object in each call (which it probably wouldn't if the lambda expression didn't capture anything, and could be implemented as a static method and the delegate cached)... but the event subscription/unsubscription will still work because the delegates are equal (referring to the same target for the same method).
If the lambda expression referred to any local variables in the method, then event handling wouldn't work, because the compiler would capture those local variables via a separate nested class containing the relevant variables and a method for the delegate code, and each method invocation of OnAutoScrollToEndChanged would create a new instance of that nested class, leading to unequal delegates.
As I say though, that's all implementation-specific... it would be better to just move that code into a separate instance method, to make it clearer that it would work.

C# Delegates and Events logic and syntax issues

As my code suggests, I'm trying to create a delegate which will point to the StringBuff method BuffString, which creates a StringBuilder that is going to have a fair amount of settings, etc.
My problem is that, for some reason, no matter what it is I try I can't pass the reference to the StringBuff class I made within my Sprite class to the delegate's constructor without receiving an error. Ontop of that, I feel like creating an event may be useful to help initiate the delegate.
The main problem is that I'm just now barely grasping these two concepts, as well as how to use them as replacements for function pointers which are allowed in other programming languages.
If anyone has any idea on what it is I need to do to make this work, I would definitely appreciate it.
Here's the code:
public class StringBuff
{
private static StringBuilder stringBuffer = new StringBuilder();
public static StringBuilder BuffString(string _string) //--may possibly have to use IntPtr to reference stringBuffer here.
//This is the equivalent to the "strbuff_new" C++ method variant, designed to update the stringBuffer.
{
int iCounter = 0;
stringBuffer.Append(_string + " ");
iCounter += _string.Length + 1;
if (iCounter == stringBuffer.Capacity - 1)
{
stringBuffer.Capacity += stringBuffer.Capacity;
}
return stringBuffer;
}
}
public delegate void UpdateStringBuffer(StringBuff sender);
public class Sprite : SpriteInterface.ISprite
{
private StringBuff stringBuff = new StringBuff();
public event UpdateStringBuffer stringBuffEvent
{
add
{
Console.WriteLine("Adding");
stringBuffEvent += value;
}
remove
{
Console.WriteLine("Removing...");
stringBuffEvent -= value;
}
}
static void Main()
{
new Sprite().stringBuffEvent += new UpdateStringBuffer(stringBuff);
}
}
I believe you are in need for some reading. Refer to the following:
Events Tutorial
Introduction to Delegates and Events
Events and Delegates simplified
You are misunderstanding the use of events and delegate.
When you want to add an Event Handler to an event, you pass a delegate of the same type as the event (which you did correctly)
But when you create a delegate, what you should pass in the constructor (most of the time) is a Method Name and not some variable, since a delegate is a kind of pointer to a (list of) functions.
I reccomend you to read more about delegates as Akram Shahda suggested but just for now i'll tell you that the method that you should pass as parameter to the delegate constructor should have the same signature - means return the same value and accept the same parameters. so for example you could have:
// This method have the same signature as UpdateStringBufferDelegate
public void SomeMethod (StringBuff buff)
{
// Doing somthing here
}
And then you can do in your main:
// Passing method's name and not a variable!!
new Sprite().stringBuffEvent += new UpdateStringBuffer(SomeMethod);
The Actuall parameters that will be passed to the function itself (some StringBuff) only determined at the time of the invokation of the event.
You should read more about that.
Good Luck!
you are doing it wrong,
new Sprite().stringBuffEvent += new UpdateStringBuffer(stringBuff);
Above code is invalid due to following reasons.
1. stringBuff that your UpdateStringBuffer is taking is an instance of StringBuff within Sprite.
2. You are accessing stringBuff from the static Main method which does not have any idea about stringBuff where it is located.
1- The delegate's constructor can only have a parameter Method. Ex
public delegate void UpdateStringBuffer(StringBuff sender);
2- You can declare ur event and add a method to define ur method in ur Splite class. Ex:
public event UpdateStringBuffer stringBuffEvent;
public ProcessUpdateStringBuffer(UpdateStringBuffer yourMethod)
{
stringBuffEvent += yourMethod
}
3- and from ur main u can define ur method to the event and invoke it like this:
Sprite sprite = new Sprite();
sprite.ProcessUpdateStringBuffer(UpdateStringBuffer(urMethod));
sprite.stringBuffEvent(ur parameters);

Categories