Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Resolved Issues with abstract members

Discussion in 'Scripting' started by God0fMagic, Aug 4, 2023.

  1. God0fMagic

    God0fMagic

    Joined:
    Feb 8, 2019
    Posts:
    16
    Hello,

    I am creating some overly complicated system and running into some issues.

    Very simplified issue:

    Code (CSharp):
    1. abstract class A : Monobehaviour
    2. {
    3.     abstract int valueA;
    4. }
    5.  
    6. class B1 : A
    7. {
    8.     override int valueA = someValue;
    9.     static int defaultValue = someValue;
    10. }
    11.  
    12. class B2 : A
    13. {
    14.     override valueA = someValue;
    15.     static int defaultValue = someValue;
    16. }
    17.  
    18. class C
    19. {
    20.     Text text; //Some UI stuff
    21.     System.Type type; //This is either B1 or B2 (or any other script derived from A)
    22.     text.text = ((A)type).valueA; //<- Can't convert System.Type to A (its abstract)
    23.     text.text = ((A)GetComponent(type)).valueA; //Object not yet instantiatiated - can't getComponent
    24.     text.text = ((B1)type).defaultValue; //Can't do because I do not know if it is B1 or B2. Need to cast to A
    25. }
    My issue is as explained in comments. Anyone have any suggestions how to go around this?

    I could instantiate a temporary object, get the default value and destroy the object but that will be very slow.
    I could also create some script that holds all the default values I want. But then I need to remember to write the data there after I create yet another script that derives from A (and there will be a lot of them)

    Idea behind the system I am making is that player gets to look at some descriptions of B1, B2... and then select which component will be instantiated based on those descriptions.

    Thanks for any solutions!
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,756
    You've identified the problem... now simplify it!

    Interfaces can let you give disparate types of objects a similar ... interface.

    In your case you might have an interface that can:

    - return the description
    - create the thing if called

    Using Interfaces in Unity3D:

    https://forum.unity.com/threads/how...rereceiver-error-message.920801/#post-6028457

    https://forum.unity.com/threads/manager-classes.965609/#post-6286820

    Check Youtube for other tutorials about interfaces and working in Unity3D. It's a pretty powerful combination.
     
  3. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,528
    It's kinda rare but nothing in your code makes any sense ^^. It's littered with syntax and spelling errors. I get that it supposed to represent some kind of pseudo code. However nothing you wrote makes sense.

    1. Fields can not be abstract or virtual, at all. So you can't override them. Maybe you meant to implement a property?
    2. Assuming a property, the actual backing field can only be initialized once by a field initializers. You can not re-initialize a field in a derived class. The only way would be a constructor to do that.
    3. The System.Type class is part of the reflection system of C# / .NET. An instance of the System.Type class contains data that describes a type but is not related to that class in the OOP sense in any way. So you can never cast a System.Type instance into a different type
    4. You can not extract the value of a field initializer without creating an instance of the class. That's just not a thing
    5. You can read a static field of the class itself, but only by using reflection. So you could use reflection to get the "defaultValue" of your class.
    6. You try to get an int value from the class and assign it to the text property of your UI text. This doesn't work without a ToString
    To get the value of the static defaultValue, you would need to do this:

    Code (CSharp):
    1.         System.Reflection.FieldInfo field = type.GetField("defaultValue", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
    2.         int val = (int)field.GetValue(null);
    3.  
    This would be a more "sane" setup:
    Code (CSharp):
    1. public abstract class A : MonoBehaviour
    2. {
    3.     public abstract int valueA { get; set; }
    4.     public static int GetDefaultForType(System.Type aType)
    5.     {
    6.         System.Reflection.FieldInfo field = aType.GetField("defaultValue", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
    7.         return (int)field.GetValue(null);
    8.     }
    9.     public static int GetDefaultForType<T>() => GetDefaultForType(typeof(T));
    10. }
    11.  
    12. public class B1 : A
    13. {
    14.     public override int valueA { get; set; } = defaultValue;
    15.     static int defaultValue = 42;
    16. }
    17.  
    18. public class B2 : A
    19. {
    20.     public override int valueA { get; set; } = defaultValue;
    21.     static int defaultValue = 5;
    22. }
    23.  
    You could use
    val = A.GetDefaultForType<B1>()
    to get the default value. However in your case since you're dealing directly with a System.Type instance, you would probably do:

    val = A.GetDefaultForType( type );
     
    Reedex, DragonCoder, SisusCo and 2 others like this.
  4. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,741
    One issue I see right away is "System.Type" variable. On line 22 you're trying to convert a "Type" into a "A". This is sort of like trying to write the word "Car" on a piece of paper and then treating that piece of paper as if it's a vehicle. System.Type describes a type, it is not that type. In this task you will probably rarely if ever need to use a System.Type variable.

    Another challenge you will have is that static variables/methods cannot be inherited. This is annoying, but it's a fact of life in C#. For this reason you will probably want to replace your static methods with a "factory pattern" - a second class which has a list of the objects you want to be able to use.
    It's either that, or you store the relevant default values outside of the class itself. Those are the two options.

    You could do individual mini-factories per type. This would give you two parallel class hierarchies. (For convenience of reference you could nest the minifactory as a subclass of the regular class; for simplicity I'm not doing that below, but that's how I'd do it in a project). So like:

    Code (csharp):
    1. public abstract class Factory<T> where T : A
    2. {
    3.    public abstract string GetDescription();
    4.    public abstract T CreateDefault();
    5. }
    6.  
    7. public class A {
    8.     abstract int valueA { get; }
    9. }
    10.  
    11. public class B1 : A {
    12.    public override int valueA => 2;
    13. }
    14.  
    15. public class B1Factory : Factory<B1> {
    16.    public override string GetDescription() {
    17.       return "Bee One";
    18.    }
    19.    public override B1 CreateDefault() {
    20.       return new B1();
    21.    }
    22. }
    Now in your class C, you can have a collection of these factories (and these could possibly even be automatically created by getting all derived classes programmatically, there's some magic sauce available on google to do that). The factories are tiny and easily iterated over to present this list to the user, and when they choose one, you call that factory's CreateDefault to make the actual object.
     
    DragonCoder and Bunny83 like this.
  5. God0fMagic

    God0fMagic

    Joined:
    Feb 8, 2019
    Posts:
    16
    Thanks for the help! Reflection worked well - just a few extra lines and no need to create any extra scripts. And while I can't force classes to implement static default value, it is very easy to tell which ones are missing it.


    The code I wrote was meant to just give a general idea of what is happening. The code inside my project is obviously different. So I'll just ignore points 1,2,3 and 6. Point 4 is my question just rephrased in a single sentence. And 5 is the answer - thanks!
     
  6. DragonCoder

    DragonCoder

    Joined:
    Jul 3, 2015
    Posts:
    1,463
    Make sure to add tons of comments to this.
    Because currently it's only you and god who knows how that construct works - and in half a year it will only be god :p (meme)
     
  7. God0fMagic

    God0fMagic

    Joined:
    Feb 8, 2019
    Posts:
    16
    There should not be any other programmers looking at this. But I am adding comments to confusing code anyway :D
     
  8. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,741
    With regard to comments and readable code, it is vital that you consider "yourself six months from now" to be a wholly separate person. You will forget everything you're in the middle of right now and wonder what you were thinking.
     
    Kurt-Dekker, DragonCoder and Bunny83 like this.
  9. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,528
    And it gets worse with age :)
     
    spiney199 and Kurt-Dekker like this.
  10. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,756
    Eventually you don't remember what you were thinking when you started writing the function five minutes ago.

    "Wait... what is this
    float d
    argument for?!"
     
    Bunny83 likes this.