B4J Question [SOLVED] StringBuilder Possible Scope Collision?

Mashiane

Expert
Licensed User
Longtime User
Ola

I hope my example here makes sense. There seems to be a scope collision between a stringbuilder defined at class_globals and one defined at sub level when the variable name is the same.

In the class I have added 2 subs, Join and Join1 to demo this, my global variable name is sb and the local variable name is sb. In Join1, the variable name is changed to sbx which brings good results. In Join where the sb is used as is, the output of the app is wrong.

I could be wrong with something, but please check.

Ta!
 

Attachments

  • StringBuilder.zip
    1.5 KB · Views: 188

Jorge M A

Well-Known Member
Licensed User
Just out of curiosity, why are you doing the "Append(xitems)" on line 17?
That reflects exactly what you're asking it to do.
B4X:
Sub Create
    Log("***incorrect")
    Dim xitems As String = Join(",",items)
    sb.Append(xitems) '<-----------------------------
    Log(sb.ToString)
    Log("***correct")
    Dim xitems As String = Join1(",",items)
    Log(xitems)
End Sub
 
Upvote 0

Mashiane

Expert
Licensed User
Longtime User
The reason for doing that is to try and explain the theory of this bug here.

1. We have defined a global variable that is a stringbuilder called sb. If we append anything to it, we should get back what was there initially and the new additions, right?

conflict.png


2. Above we have 2 subs, one named Join and the other Join1. The stringbuilder variable in Join is named sb and the one in Join1 named sbx.

Now, if one would run.

B4X:
Log(Join(",", Array("b4x"," is"," cool")))

it will print this only.

B4X:
b4x, is, cool

Right? Join1 will also do the same

3. Now...

We have defined a global variable named sb as stringbuilder in our class

B4X:
Private sb As StringBuilder

4. When the class is initialized, we add some content to this sb variable

B4X:
sb.Initialize
    sb.Append("Mashy...").Append(CRLF)
    sb.Append("Mashy Again...").Append(CRLF)

5. In the main code, we define and initialize the class.

B4X:
Dim cls As SBClass
    cls.Initialize

If you would change sb to Public in the class and execute log(cls.sb.ToString) it will show..

Mashy...
Mashy Again...


Now if, we append anything to this global sb stringbuilder variable, it should add to the already existing content. To test our theory here, we will create a list of items and then join them using the stringbuilder methodology. To do this we will create a subroutine to join items in a list using a stringbuilder class. These are the two subs above one with sb and sbx as stringbuilders.

6. Running Join which gives wrong output (using sb local variable)

B4X:
Sub Create
    Dim xitems As String = Join(",",items)
    sb.Append(xitems)
    Log(sb.ToString)
End Sub

Result

a,b,ca,b,c

7. Running Join1 which gives correct output (using sbx local variable)

B4X:
Sub Create
    Dim xitems As String = Join1(",",items)
    sb.Append(xitems)
    Log(sb.ToString)
End Sub

Result

Mashy...
Mashy Again...
a,b,c


The only difference between the two subroutines is the variable name between sb and sbx. With Join, all the contents of the global sb are overwritten. With sbx, the correct code execution happens. Thing Dim sb as stringBuilder inside Join is supposed to be having a local scope to that sub ONLY, however it's affecting the global variable scope. Thus this thread. I hope you understand.

Ta!
 
Upvote 0

Mashiane

Expert
Licensed User
Longtime User
I hear your arguement and there I ended up changing the variable name to avoid the conflict, thing is I have not had this issue before with the StringBuilder and have always defined most of my stringbuilder variables as sb whether locally or globally.

I have looked at the transpiled code sbclass.java, and for Join and Join1, there is something different.

B4X:
public String  _join1(String _delimiter,anywheresoftware.b4a.objects.collections.List _lst) throws Exception{
int _i = 0;
anywheresoftware.b4a.keywords.StringBuilderWrapper _sbx = null;
String _fld = "";
 //BA.debugLineNum = 40;BA.debugLine="private Sub Join1(delimiter As String, lst As List";
 //BA.debugLineNum = 41;BA.debugLine="Dim i As Int";
_i = 0;
 //BA.debugLineNum = 42;BA.debugLine="Dim sbx As StringBuilder";
_sbx = new anywheresoftware.b4a.keywords.StringBuilderWrapper();

and Join

B4X:
public String  _join(String _delimiter,anywheresoftware.b4a.objects.collections.List _lst) throws Exception{
int _i = 0;
String _fld = "";
 //BA.debugLineNum = 25;BA.debugLine="private Sub Join(delimiter As String, lst As List)";
 //BA.debugLineNum = 26;BA.debugLine="Dim i As Int";
_i = 0;
 //BA.debugLineNum = 27;BA.debugLine="Dim sb As StringBuilder";
_sb = new anywheresoftware.b4a.keywords.StringBuilderWrapper();

The only difference is this line...

B4X:
anywheresoftware.b4a.keywords.StringBuilderWrapper _sbx = null;

Me thinks that if this will not break much of the code, when transpiling...

1. If a variable is Dim-med as a List, Map, StringBuilder e.g. Dim X as List, Dim X as Map, Dim X As StringBuilder
2. Is this Dim? inside, "_class_globals" ?
3. If No, then anywheresoftware.b4a.keywords.StringBuilderWrapper _sbx = null; for example before the _new call.

If its not possible perhaps the IDE can detect when variables that are already defined globally are being Dimmed again and throw an exception on compile!

Please @Erel can you consider looking at this? Ta!
 
Last edited:
Upvote 0

Mashiane

Expert
Licensed User
Longtime User
Noted with thanks and I dont mean to be a pain, but I must kindly request to please be provided a working example that demonstrates this, specifically for the StringBuilder for now. I'm really failing at grasp how to make this work. I have included my broken example here.

My code currently returns..

B4X:
a,b,ca,b,c

with Join

instead of

B4X:
Mashy...
Mashy Again...
a,b,c

with Join1

There is so much code that I need to apply this solution to and I am stuck here! Eish!

Thanks!
 

Attachments

  • StringBuilder.zip
    1.5 KB · Views: 177
Upvote 0

Mashiane

Expert
Licensed User
Longtime User
however when I run your project
Sorry, that's the working example where I have used different variable names for the sb variable.

Can you please change the create method in the class to be

B4X:
Sub Create
    Dim xitems As String = Join(",",items)
    sb.Append(xitems)
    Log(sb.ToString)
End Sub

So that it uses Join and not Join1. What Join does is to dim a global variable inside a sub, which is built from items from a list. That result is then fed to the globally defined variable.

PS: This was just my silly example, all I need is just a working example for a global StringBuilder variable that is dimmed at sub level.

Thanks!
 
Upvote 0

Erel

B4X founder
Staff member
Licensed User
Longtime User
I don't see anything unexpected here.
When you dim and initialize sb inside Join you "reset it". At the end of this sub the value will be a,b,c. You then add this value to sb again. So the output is a,b,ca,b,c.

As written several times in this thread the 'sb' variable in Join sub points to the global sb variable.
 
Upvote 0

Mashiane

Expert
Licensed User
Longtime User
the 'sb' variable in Join sub points to the global sb variable.
Thank you so much. I finally get it. I didn't want to point to the global sb variable, I assumed it was going to be local. Damn! I have been coding so badly all my life with b4x in this instance.

Just a favour then, if possible, can you add something to the IDE just to warn when a global variable that follows this approach is dimmed locally again? For slow learners like me that could do a great deal of help. This is so incredible. Little knowledge is dangerous hey.

Thanks a mill!
 
Last edited:
Upvote 0

Erel

B4X founder
Staff member
Licensed User
Longtime User
warn when a global variable that follows this approach is dimmed locally again?
The IDE shows you that it is a global variable. It paints the variable with purple color.

A warning for this will add many "false positives" as there are many valid cases where you would like to redim a global variable.
 
Upvote 0
Top