What is Span in C# and why you should be using it

2021 ж. 12 Шіл.
240 940 Рет қаралды

Become a Patreon and get source code access: / nickchapsas
Check out my courses: dometrain.com
Hello everybody I'm Nick and in this video I am going to talk about Span of T in C#. Span was introduced in C# and .NET alongside a series of optimisations all the way back in .NET 2.1 and it has come to my attention that it is really confusing to understand if you just read the documentation for it. In this video I will break it down for you and help you understand exactly what it is, how it works and how you can use it in your applications today!
Don't forget to comment, like and subscribe :)
Social Media:
Follow me on GitHub: bit.ly/ChapsasGitHub
Follow me on Twitter: bit.ly/ChapsasTwitter
Connect on LinkedIn: bit.ly/ChapsasLinkedIn
Keep coding merch: keepcoding.shop
#csharp #dotnet #span

Пікірлер
  • Hello everybody. As some people have already pointed out, after 13:50, when I'm returning a ReadOnlySpan, the ToString() on line 21 should be removed. I didn't notice because of the implicit operator. If you leave the ToString() in then you still allocate the string you return. - Keep coding

    @nickchapsas@nickchapsas2 жыл бұрын
    • Great video. But where could I check source code?

      @lipatovsa7@lipatovsa72 жыл бұрын
    • @@lipatovsa7 The source code is available to my Patreons

      @nickchapsas@nickchapsas2 жыл бұрын
    • Thought you were just testing if we paid attention!

      @PeterManger@PeterManger2 жыл бұрын
    • @@nickchapsas What would be the benefit/differences in using method(in string text) method(ref string text) ?

      @roflex2@roflex22 жыл бұрын
    • @@roflex2 strings are immutable in C#. Even if you pass down explicitly by reference your can't change the value of the string. You just point to a new string reference type

      @nickchapsas@nickchapsas2 жыл бұрын
  • I'm a systems developer and primarily work with low-level languages like ASM, C, C++, etc., so I don't have a lot of experience on the intricate details of optimizations for managed languages. Explanations like these are invaluable and I find them immeasurably useful so I thank you very much for this.

    @GeraldOSteen@GeraldOSteen2 жыл бұрын
    • Yeah typically managed languages have to add complex features to get the same speeds we get using lower level languages. Its a big trade off in my opinion. In those languages you usually either have to swallow performance penalties or readability penalties. In languages like c++, using a pointer isnt going to confuse anyone. So really I prefer those languages, but it is cool to learn the intricacies of these languages. In particular because I use it for a my job

      @KayOScode@KayOScode2 жыл бұрын
    • C++ also introduced similar concepts as standard, and before that there were 3rd party libraries providing some kind of span. See std::string_view and std::span

      @minciNashu@minciNashu2 жыл бұрын
    • @@minciNashu arent those features kind of just bloat though. We have void* and thats all we really need

      @KayOScode@KayOScode2 жыл бұрын
    • My first thought was, I mean, great, but why do I get the impression that C# has a lot of tricks for solving problems caused by C# in the first place? ;) Why not just have a span method on the string that returns a readonly reference to a section of the string?

      @mikicerise6250@mikicerise62502 жыл бұрын
  • I'm so impressed by these explanations. Honestly, the best thing to happen for a junior dev, is to find your channel. Great explanations, very helpful videos with in-depth knowledge and analysis. Thank you, please keep doing them!!

    @chrisd961@chrisd9612 жыл бұрын
  • I love these types of videos that explain the standard classes in the framework that help you write more efficent code. These are the types of things I don't stumble upon when researching how to solve a problem. I would love to see more videos like these!

    @Manlyman789@Manlyman7892 жыл бұрын
  • To me, these are the best technical videos on the net, even though about 95 percent of them are over my head. This video, however, was worthy of getting a bowl of popcorn, sitting back and just watching. Thanks a bunch, Nick.

    @binjozoken6055@binjozoken60552 жыл бұрын
  • I'm a senior developer with 10+ years of experience and I learn so much from your videos thanks in advance

    @Pouya..@Pouya.. Жыл бұрын
  • When I first watched Microsoft themselves explain Span, I was lost and confused. They have a knack for making something sound way more convoluted and complex than needed when they explain new concepts. This video made it all click instantly. Thank you very much, I can't wait to start using Span in my own work.

    @evolvedant@evolvedant2 жыл бұрын
    • The same is true for their documentation

      @Faygris@Faygris Жыл бұрын
    • ye microsoft love overcomplicating every single one of their examples.....they need to hire people to teach them to keep things simple. Especially for their documentation.

      @mrx10001@mrx10001 Жыл бұрын
  • Span is a really under-rated feature, not just for performance, but also my favourite pun in C# - TimeSpan

    @sasukesarutobi3862@sasukesarutobi38622 жыл бұрын
    • Spantastic pun 😉

      @RichardNobel@RichardNobel2 жыл бұрын
    • If you have a dog... it's probably a Spaniel ? 🐕

      @RichardNobel@RichardNobel2 жыл бұрын
    • Lately the news has been so dull I've tuned into Cspan

      @jeffwilson8246@jeffwilson82462 жыл бұрын
    • @@jeffwilson8246 This whole thread makes as much sense as watching C while only wearing a . I don't get the OP. I understand span and TimeSpan, but I don't see the pun. I am the dummy cause 40+ people saw it. Guess I need to get out of my static internal scope of thought.

      @tuck1726@tuck17262 жыл бұрын
  • Thank you for this video. As Unity 2021 LTS now supports spans this gave a really good introduction and explanation on how to use them. Also the ref struct is valuable information since I have some very short lived structs for triangles and other mesh-related objects that only live for the duration of the method.

    @ristopaasivirta9770@ristopaasivirta9770 Жыл бұрын
  • Didn't learn anything new in this, but I'm impressed by your presentation. I would have more quickly learned how Span works if this was the first video I saw about it.

    @DepressionAlgorithm@DepressionAlgorithm2 жыл бұрын
  • Great explanation, Nick, yet again! Thank you for taking the time, in your very informative videos, to show us what's happening "behind the scenes". On the heap, stack, etc. 🙏🏻

    @RichardNobel@RichardNobel2 жыл бұрын
  • this is the first explanation of Span that made sense to me. thank you!!

    @kodikodi9948@kodikodi99482 ай бұрын
  • hey Nick, I have been watching your videos for a while. Just want to thank you so much for the learnings I got from them. You cannot imagine how helpful you are to people like me. I am using these learnings in a software solution I am developing myself already for a year. Again, thanks!!! Andre from Portugal

    @AndreMauricio4@AndreMauricio42 жыл бұрын
  • Great video. This is a topic I've been half-aware of for a while, but seeing it in context helps a lot.

    @jackkendall6420@jackkendall64202 жыл бұрын
  • As usual, you explain the nuts and bolts, the theory, and the benefits, all better than the documentation and anything else I’ve found.

    @JoeEnos@JoeEnos2 жыл бұрын
  • Your video's are awesome. I very much appreciate the technical in-depth explanations of them. Thanks a ton!

    @snuffsix9598@snuffsix95982 жыл бұрын
  • Had missed the memo on this one. Thanks, this was really informative!

    @Deathhead68@Deathhead682 жыл бұрын
  • Span is a powerful structure but has some limitation, what about Memory and what is the difference between them

    @AB-fb1ve@AB-fb1ve2 жыл бұрын
  • Nothing new under the sun but very well explained. It's useful to have this kind of videos around, proper knowledge should be distributed like this.

    @Sgro81@Sgro812 жыл бұрын
  • I really appreciate your content Nick, helped me a lot improve the way i code :)

    @pedrocunha4322@pedrocunha4322 Жыл бұрын
  • Curious to learn how it deals with byte arrays, compared to working with strings like you demo'd.

    @KeesSchollaart@KeesSchollaart2 жыл бұрын
  • Isn't the ReadonlySpan allocating at least a copy of the string on the heap? For example, consider: string s = "123"; ReadOnlySpan span = s; Console.WriteLine(int.Parse(span.Slice(0,2))); s = "321"; Console.WriteLine(int.Parse(span.Slice(0,2))); produces: 12 12 Oh, since strings are immutable in this language, the second time we assign to »s« we actually perform a second heap allocation, and »span« can happily use the address to which »s« pointed to when »span« was defined. And some smart-pointer-like stuff.

    @marksmod@marksmod Жыл бұрын
  • if I understand this well, when you use the span, like: Readonly dateAsSpan = _dateAsText; you make a new variable called "dateAsSpan" but it's not allocating new memory for the "value" itself (in the heap), but instead just basically stores a reference, similarly when in c (normal C, not ++ or sharp) if I had a function like this void double_it(int *j) { j *=2 } using "int *j" instead of "int j" (* means that you pass by memory address reference instead of value)

    @Cruz0e@Cruz0e2 жыл бұрын
  • Your short brought me here. Good stuff!

    @rainezombi3431@rainezombi3431 Жыл бұрын
  • Very nice explanation. Will surely be helpful in my work. Thanks!

    @Sad-Lemon@Sad-Lemon2 жыл бұрын
  • Amazing delivery. Thank you!

    @arlvinmoyo9290@arlvinmoyo9290 Жыл бұрын
  • When you talk about allocation, it's important to stress that span doesn't really copy the source memory on the stack. The span object itself - containing probably a starting pointer and a max size - is created on the stack.

    @minciNashu@minciNashu2 жыл бұрын
    • Very true👆He should explaine it, otherwise it's misleading

      @Maxi-xw1jb@Maxi-xw1jb Жыл бұрын
  • Good video, very insightful !

    @inxaneofficial7756@inxaneofficial77563 күн бұрын
  • Brilliantly explained, as per usual.

    @davemasters@davemasters Жыл бұрын
  • Great explanation, Nick 👍

    @PontusWittenmark@PontusWittenmark2 жыл бұрын
  • Great Video, thanks for the board explanation, it was awesome!

    @coffee22able@coffee22able2 жыл бұрын
  • Another great video Nick! A small question - is there a way to split Span into array\list of Spans? Or actually, what is the best way to do it, without iterating by myself over the Span?

    @tzurdo1@tzurdo12 жыл бұрын
  • Its nice to see that D is benefiting C#

    @user-py9cy1sy9u@user-py9cy1sy9u2 жыл бұрын
  • Excellent explanation, Nick! Thank you very much. :)

    @Cassiopeja22@Cassiopeja222 жыл бұрын
  • Wow, I never see before but I will us it in the next projects -- thank you.

    @andreaskarz@andreaskarz2 жыл бұрын
  • Quality content as always !

    @jonowilliams26@jonowilliams262 жыл бұрын
  • That memory view in rider is awesome

    @tehsimo@tehsimo Жыл бұрын
  • you should make a follow up video on ref structs if you haven't already.

    @xinzhouping@xinzhouping2 жыл бұрын
  • This is... I can't believe how many times this could've helped me...

    @m0ment219@m0ment2192 жыл бұрын
  • Nice explanation. Thank you!

    @KeithSwanger@KeithSwanger2 жыл бұрын
  • Excellent video and presentation.

    @misomalu@misomalu2 жыл бұрын
  • Thank you Nick. Your videos are easy to understand, neat and to the point. I looked on your web site at the courses and wanted to know if the Dependency injection was based on a third party app. I was unable to locate a way to contact you there.

    @im1in260m@im1in260m Жыл бұрын
  • You are amazing! Thanks for your explanations

    @user-ss9qg6ke5t@user-ss9qg6ke5t Жыл бұрын
  • In your last example you changed the return type but forgot to remove the ToString call from the method, negating the performance benefit. But thanks for explaining Span (and ref struct) in an understandable way; neither of those ever made sense to me until now!

    @EdKolis@EdKolis2 ай бұрын
  • Nicely explained!

    @hirenpatel2236@hirenpatel22362 жыл бұрын
  • Hi Nick, great video as usual :)

    @milanmladenovic@milanmladenovic2 жыл бұрын
  • Marvelous ! To the point. Respect !

    @rifatislamrakesh@rifatislamrakesh Жыл бұрын
  • Great presentation, thank you!

    @my_temporary_name@my_temporary_name2 жыл бұрын
  • Great video as always! in 2:26 how do you get to that debugger window with the memory tab ?

    @amirdar@amirdar Жыл бұрын
  • wohoaaaa!!! now i understand a bit more the use span and garbage collectors....thanks nick!!

    @scabendlin@scabendlin Жыл бұрын
  • Great video. I believe application only stops (completely) for GC when using workstation GC as opposed to server GC - you should compare the differences as they are quite striking and too long to go into here.

    @bovineox1111@bovineox1111 Жыл бұрын
  • Hi. Thank you for the tutorials. I have learned a lot. I wanted to know how do you get the results inline. Thank you.

    @JtendraShahani@JtendraShahani Жыл бұрын
  • Brilliant as usally, Nick :-)

    @gbelkin4@gbelkin42 жыл бұрын
  • Huh, I do understand that returning the Span is advantageous but in your example you return 'yearAsText.ToString()' althought the return type is 'ReadOnlySpan'. What happened there? Some implicit conversion?

    @PaulSinnema@PaulSinnema Жыл бұрын
  • Great vid easy to follow thankyou

    @stuartbooth8232@stuartbooth82322 жыл бұрын
  • That is a great video, thanks, Nick. May I ask what IDE you are using?

    @tianjinghan1@tianjinghan1 Жыл бұрын
  • This is excellent for string manipulations

    @SuperSpeed52@SuperSpeed522 жыл бұрын
  • 4:18 benchy was such a cute name 😍😂 didn't see that coming

    @marcomarek7734@marcomarek7734 Жыл бұрын
  • Great vid Nick, and there's me still working in c#7, .NET 4.8 & WinForms... I'm so far behind these days, but good to see new features in c#. I just wish the company I worked for didn't work on 15-20 year old projects...

    @harag9@harag92 жыл бұрын
    • don't worry, it's the same for me. Working with .Net framework 4.6.1 ! But i never stop learning new technologies. It is the only way

      @aminejadid2702@aminejadid27022 жыл бұрын
    • @@aminejadid2702 Same here, but I'm now losing interest doing it in my spare time, so unless I do it at work I lose the new skill.

      @harag9@harag92 жыл бұрын
    • Add the System.Memory Nuget package. It's not everything that you get with core, but you get some benefit.

      @RealisableSoftware@RealisableSoftware2 жыл бұрын
    • @@harag9 You can always find a better job.

      @aminejadid2702@aminejadid27022 жыл бұрын
    • .net 4.0 for me at work 🤣

      @horowitzhill6480@horowitzhill64802 жыл бұрын
  • So this is how you use memory tab in Rider!

    @10199able@10199able2 жыл бұрын
  • thank you so much

    @paultaylor2054@paultaylor2054 Жыл бұрын
  • good stuff! thanks for sharing.

    @HomeSlize@HomeSlize2 жыл бұрын
  • Phenomenal!!!

    @chiragdarji1571@chiragdarji1571 Жыл бұрын
  • Does int.Parse accept a Span? or theres a implicit conversion from Span back to string?

    @CodingGustavo@CodingGustavo2 жыл бұрын
    • There is an overload with Span yeah

      @nickchapsas@nickchapsas2 жыл бұрын
  • Thank you very much

    @easycodeunity3d14@easycodeunity3d142 жыл бұрын
  • In comparison to C- span sound like a pair of pointers, and slice moves one of them. I hope I got the idea of this feature thanks!

    @yonatankarni8867@yonatankarni8867 Жыл бұрын
  • I **think** there is a little bug at the end of the video. Line 21 you are calling and returning `ToString()` As I just learned in your video, this would allocate memory on the heap requiring GC at some point. Again, this is all stuff I learned from YOU. So thank you

    @shoooozzzz@shoooozzzz8 ай бұрын
    • Aaaaaaaaaaaaaaaaaaaaaaand I just saw the pinned comment 🤦🏻‍♂ Oh well. Guess this shows I'm paying attention and learning stuff

      @shoooozzzz@shoooozzzz8 ай бұрын
  • great example m8 ty for the presentation

    @TheCMajor9th@TheCMajor9th2 жыл бұрын
  • You should have maybe ran the test with a changing date every time. Strings in c# are instantiated once per instance and re-used. So "foo" in variable a and "foo" in variable b are both the same "foo" in memory. This likely will show a more realistic real world use case.

    @digitalpacman@digitalpacman2 жыл бұрын
  • Hi, Thanks for your great video, Please record a view about diagnostics and tracing in .NET 5.

    @mehdihadeli@mehdihadeli2 жыл бұрын
  • Nice content!

    @MarioRamosMontesinos@MarioRamosMontesinos Жыл бұрын
  • Very nice and helpfully 🎉

    @tobiasj8019@tobiasj801910 ай бұрын
  • The Span.ToString() method would allocate heap memory because strings are immutable, right?

    @TheAndiKurz@TheAndiKurz6 ай бұрын
  • Nice, thanks for that!!!

    @bomite@bomite2 жыл бұрын
  • Nice! I learned a new C# feature. Span look like pointers, so I'd have liked to know what would have happened if dateAsText had changed, and make sure if span really works like pointers. (: BTW i appreciate your video.

    @eperez_yt@eperez_yt2 жыл бұрын
  • So it's like a StringView, providing a view into the string pretty much.

    @SealedKiller@SealedKiller2 жыл бұрын
    • True For those who are not aware - It's std::string_view (after you #include ) in C++ 17

      @igorthelight@igorthelight2 жыл бұрын
  • The best👌👌

    @bhavinmistry6262@bhavinmistry62622 жыл бұрын
  • Very good video - has helped to clear up my understanding of Span :)

    @Radictor44@Radictor44 Жыл бұрын
  • If int.Parse did not need to input string but span why Console.WriteLine need it?

    @mabakay@mabakay2 жыл бұрын
  • At 10:40, wouldnt it be an offset of 2 instead of 3? Im guessing that its a 0 based offset, so the first value in the string would be index 0 and the third would be 2, so if we want to start reading the month which starts at index 2 the offset would then have to be 2 right? (+2 offset and 2 length).

    @Aaron31056@Aaron31056 Жыл бұрын
  • Hello, great video as usual :) Do the .ToString() really needed in the YearAsText() method return ? (end part of the video)

    @Dawhun@Dawhun2 жыл бұрын
    • It did because at that point it is a ReadOnlySpan not a string and the Console.WriteLine method doesn't have an overload for it.

      @nickchapsas@nickchapsas2 жыл бұрын
    • @@nickchapsas I was wondering about YearAsText, line 21. Would the `ToString()` there still allocate on the heap and then implicitly converts the string to the ReadOnlySpan? I would’ve expected `return yearAsText` without the `ToString()`

      @TylerEich@TylerEich2 жыл бұрын
    • @@TylerEich No you don’t need to do ToString(). What you would be returning is a readonly ref struct to the caller and in there you can do the ToString()

      @metaltyphoon@metaltyphoon2 жыл бұрын
    • @@TylerEich Sorry, Yeah not that you pointed out the line I understand what you mean. No that's a mistake. I added a pinned comment to explain that. I missed it because of ReadOnlySpan's implicit operator.

      @nickchapsas@nickchapsas2 жыл бұрын
  • perfect

    @OvRaf@OvRaf8 ай бұрын
  • Great content! how does span compare to string builder in such scenarios?

    @Guciubla@Guciubla2 жыл бұрын
    • string builder is used to create a string without having to deal with the immutability concerns. Span, even tho it could technically be used for something similar, primarily does the opposite.

      @nickchapsas@nickchapsas2 жыл бұрын
  • Is Span.Slice.ToArray() slightly faster than Buffer.BlockCopy or byte array copy using unsafe methods? (seems to be yes)

    @relaxfunplaynice@relaxfunplaynice Жыл бұрын
  • Great video as always. Is this only ever useful for strings? Thanks

    @Ayomikun@Ayomikun2 жыл бұрын
    • A span resembles an array so it can work as a byte array, int array and so on

      @nickchapsas@nickchapsas2 жыл бұрын
  • This is great.

    @rossthemusicandguitarteacher@rossthemusicandguitarteacher2 жыл бұрын
  • this is brillant

    @mehmetedex@mehmetedex2 жыл бұрын
  • 6:15 - You could access String as an array too! But if you would try to change even just one char like that: string myString = "Hello!"; myString [0] = 'h'; // This will not be compiled! It still would allocate a whole new string. StringBuilder do not behave like that tho so you could do that: var sb = new StringBuilder("Hello!"); sb[0] = 'h';

    @igorthelight@igorthelight2 жыл бұрын
    • The indexer of a string in C# is get only. What you wrote is invalid code.

      @Dennis19901@Dennis199012 жыл бұрын
    • @@Dennis19901 Thanks! I pointed out that this line can't be compiled.

      @igorthelight@igorthelight2 жыл бұрын
  • So I wonder if there is a similar use of SPAN for a more common task of parsing CSV comma delimited strings?

    @DynotoeTube@DynotoeTube2 жыл бұрын
    • Yeah that's a great usecase

      @nickchapsas@nickchapsas2 жыл бұрын
  • I guess that Parse method have span override. But what is happening if we need to use method which will except string. I guess it will have implicit conversion there.

    @VasoPerasLikodric@VasoPerasLikodric2 жыл бұрын
    • If the method needs a string then the return string will be allocated but you can prevent any potential allocation during the mid-way processing in the method, depending on the workload. Also yeah, int.Parse has a span overload, including many other things that used to accept string.

      @nickchapsas@nickchapsas2 жыл бұрын
  • Does the StringBuilder class (which I have been told is more efficient than simply using String) use Span behind the scenes?

    @cyberherbalist@cyberherbalist2 жыл бұрын
    • Some of the StringBuilder implementation has been updated to use that behind the scenes where possible

      @nickchapsas@nickchapsas2 жыл бұрын
  • Well, I've searched for span class in MSDN. But I couldn't find a "Slice" method in that page.

    @kcvinu@kcvinu2 жыл бұрын
  • Amazing

    @neriacohen5490@neriacohen5490 Жыл бұрын
  • The stack Size is 1MB. Will it get full?

    @1Eagler@1Eagler2 жыл бұрын
  • Could someone advise, why he is talking about the stack? It seems Span or ReadOnlySpan doesn't allocate in stack? You need to use stackalloc to place something in stack first

    @Maxi-xw1jb@Maxi-xw1jb Жыл бұрын
    • All local variables (including the method's parameters) are on the stack. Each method call allocates a so called stackframe which consists of the code address to jump back to when the method "returns", the value that the method returns, followed by arguments, contents of local variables and potentially any stackalloc stuff (not sure if dynamically allocated stack memory is considered part of the method's own stackframe or not but who cares). Take it with a grain of salt though, I'm definitely not an expert on this. The Span or ReadOnlySpan structs are ref structs and can ONLY be used as local variables (normal structs can live on the heap, for example inside a field of a class or be boxed directly by casting them to object but ref structs can't be boxed and can't be used as fields in classes or in structs that can be boxed - meaning there is no way for the content of a ref struct to ever appear on the heap) and are therefore always stack allocated. The memory they point to may be heap allocated (like a ReadOnlySpan referencing part of a string or Span of a char array). In C/C++ you'd just pass a pointer to any item inside a stack or heap allocated array and the count of items you want the callee to work on, "creating" a span of sorts. From the compiler's or cpu's perspective it's just like a regular array because it has no knowledge about where the original array started - or not even that, it's really just a pointer and a number. The Span struct mimics that as far as I understand it but more safely or something.

      @user-qg2dg9vu8w@user-qg2dg9vu8w10 ай бұрын
  • With respect to writing a Span to the console, I presume you can use Console.Write(char) while maintaining its benefits? Indeed, perhaps there's a built in Console.WriteLine(Span) method? (I've not checked)

    @IAmAI101@IAmAI101 Жыл бұрын
    • There is but that wasn’t the point of the exercise. I assume that the user needs a concrete string back so I don’t break the code contract. Even the span overload does a ToString internally and allocates the string so it’s not magic

      @nickchapsas@nickchapsas Жыл бұрын
    • @@nickchapsas Interesting. Would it be more efficient for the WriteLine method to not allocate a string and just repeatedly call Write(char)?

      @IAmAI101@IAmAI101 Жыл бұрын
  • Man, if do I use span with largest strings, can I do stack overflow?

    @gabrielsantana5153@gabrielsantana5153 Жыл бұрын
  • at 00:03:14 ,if it slows things down, can you ask garbage collection not to free up the garbage until the entire program is done running? i mean, thats what memory is for right? let the entire program finish before doing any sort of expensive garbage collection?

    @GuidoSalami1@GuidoSalami110 ай бұрын
  • In order to make use of the Span 'value' you still need to convert it ToString(), which as you say loses the value in Span, unless the resulting string is a concatination of a bunch of Span.Slice functions. So you could take the fact that Span is basically an Array and then join them together to produce the final result.

    @rajm1976@rajm19762 жыл бұрын
    • As you can see in the pinned comment the ToString() in the method that returns a ReadOnlySpan was a mistake. You don't need it and if you don't use it you don't allocate until the final ToString()

      @nickchapsas@nickchapsas2 жыл бұрын
  • Awesome!

    @easycodeunity3d14@easycodeunity3d142 жыл бұрын
KZhead