C
C#8mo ago
__dil__

❔ How to structure project and import items?

I'm just starting with C#, and I'm having trouble understanding how project structure and importing works. I come from Rust where you can import a single item from a module with something like
use the_module::TheItem;
use the_module::TheItem;
You can also import multiple items using this notation:
use the_module::{Item1, item_2};
use the_module::{Item1, item_2};
I tried something similar in C#:
using my_namespace.TestClass;
using my_namespace.TestClass;
But that gives an error Namespace name expected .... Is it not possible to only import some items from a namespace? I'm also having trouble understanding the relationship between namespaces and files. It seems like you can add stuff to a namespace from anywhere inside your project. So it seems like namespaces are not really self-contained modules, but more like heaps where you can throw in anything semi-related to some concept. Finally it seems to me like you can't have items private to a module? This all seems rather unstructured to me, so surely I must be missing something. I can summarize my questions as such: 1. Can you import only specific items? 2. What's the idiomatic way to structure complex libraries? namespace per file? 3. What are the tools to restrict visibility such that I can have parts of my library that are only available internally? Please bear in mind I'm not familiar with "old-school" OOP stuff so I might be oblivious to the obvious 🙂 I would highly appreciate it if you could explain how it works.
41 Replies
Pobiega
Pobiega8mo ago
"import" isnt really a concept in C# as such. using namespace.namespace.namespace; just allows you to "use" the things from the target namespace without fully qualifying it even without the using statement, you can still use them with their full name like Namespace.Type varName = new Namespace.Type(); the closest thing we have to rusts modules are the C# assemblies each project compiles to its own assembly, and its fairly common to split a larger codebase into multiple projects namespaces traditionally reflect your file structure, but there are exceptions
Finally it seems to me like you can't have items private to a module?
Sure you can, thats internal (as opposed to public or private) it means "only this assembly can use this type"
Jimmacle
Jimmacle8mo ago
for 2, namespaces normally reflect the folder structure of your project (so one namespace per folder, not file) technically you could "import" individual types using type aliases but i don't think i've ever seen that used except in the case where you have ambiguous types
Pobiega
Pobiega8mo ago
^
Jimmacle
Jimmacle8mo ago
and yeah, if you're developing a library then simply marking types as internal instead of public will hide them from consumers
Mayor McCheese
Mayor McCheese8mo ago
Usually in my experience this is a tooling gaffe; not intentional.
Jimmacle
Jimmacle8mo ago
yeah, like i said i've never seen someone actually do that
__dil__
__dil__8mo ago
Thank you both @Pobiega @Jimmacle, that does clear things up a bit. So, for "importing specific items" (I now understand this is not quite the correct terminology), my options are either 1) qualify the items everywhere, or 2) deal with the fact that I'm importing everything in the module? As for assemblies and internal, this is good to know, but still a bit unfortunate because in Rust it's very common to have many very contained modules that only export their public API and keep all the internals private. The goal is not only to keep things away from consumers, but also to restrict which parts of a codebase can interact with each other's internals. I imagine it's not super good practice to have a ton of assemblies, and AFAIK you can't nest assemblies. But anyways, since this is the way things are done in C# I'll learn and adapt 🙂
Pobiega
Pobiega8mo ago
You can make nested classes, and have a nested class be private that would make it unusable from outside that class
Mayor McCheese
Mayor McCheese8mo ago
I have some data scenarios where I use private nested classes for that reason.
Jimmacle
Jimmacle8mo ago
same
__dil__
__dil__8mo ago
Is it a common pattern, to use a whole class as a module? That is very foreign to me.
Pobiega
Pobiega8mo ago
"module" is foreign to us btw 😛
Jimmacle
Jimmacle8mo ago
yeah i'm not super clear on what expectations there are for a "module" as a filthy non-rust-user
Pobiega
Pobiega8mo ago
but based on my very limited understanding of rust, I'd say... kinda? 😄
__dil__
__dil__8mo ago
well, I'm the beginner here, so you'll have to excuse me 🙂 I don't have the right terminology.
Mayor McCheese
Mayor McCheese8mo ago
An "assembly" could be a single class, but frequently that's not the case.
__dil__
__dil__8mo ago
I'm learning though!
Jimmacle
Jimmacle8mo ago
as for 2, there isn't really a penalty for this besides maybe making it harder to find what you're looking for in autocomplete
__dil__
__dil__8mo ago
to me, a module is a list of items (types, functions, etc), which can declare the items as private or public. Modules can be nested, and nested modules have access to their parents internals. Parent modules don't have access to their childen's internals. This is not super important, but I thought I'd clarify to make my understanding clearer. Referring to namespace-per-file? I don't mind namespace-per-folder if that's what's usually done. I'm just trying to understand best practices and such. Visibility is a huge deal in Rust so that's why I might seem over-focused on that. But ultimately I just want to do C# the right way.
Jimmacle
Jimmacle8mo ago
i just mean there's no impact on compile times etc. (at least not that i've run into)
Pobiega
Pobiega8mo ago
namespace-per-folder is the default, and most if not all editors do it by default
Jimmacle
Jimmacle8mo ago
yeah, afaik rider's defaults will nag you to make your namespaces match your folder structure
reflectronic
reflectronic8mo ago
there is no system for fine-grained imports in C#. it does not really make sense for .NET, where the smallest unit of organization is the assembly. as soon as you reference a DLL, all of the types (which are visible to you) are already "imported"
Pobiega
Pobiega8mo ago
VS too we don't have top level functions as such. all methods, including static methods, must belong to a class (or other type), so the only group of things we have in a namespace are types
__dil__
__dil__8mo ago
oh! No free functions? That is strange 🤔 But good to know!
reflectronic
reflectronic8mo ago
in other languages, fine-grained imports may make more sense, because the types really cannot be used without the import. in JavaScript, importing something runs some code, and if you don't run the code, the things you're trying to import just aren't there. but in .NET, once you have referenced the standard library, System.Collections.Generic.List is always there. in C#, you can always reference it by its fully-qualified name, even without a using statement. so having fine-grained imports does not bring much to the table
Pobiega
Pobiega8mo ago
might feel strange at first yep.. but if you make a static class with only static methods in it... thats essentially a module from rust a budget module, or a "we have modules at home" module perhaps
__dil__
__dil__8mo ago
I see, this is all good to know, thanks again
reflectronic
reflectronic8mo ago
the closest thing to a module, i guess, is a static class
__dil__
__dil__8mo ago
IMO the benefit is more about dev experience and not so much about how the compiler handles linking and whatnot. using a namespace in C# seems to be akin to doing use my_module::*; in Rust which is considered bad practice since it pulls at ton of potentially useless crap into scope and increases the chance of name collisions.
reflectronic
reflectronic8mo ago
in C#, you can use using static X; to directly import all static members of a class. so, for example,
using static System.Console;

public class Program
{
static void Main()
{
// I did not have to qualify Console
WriteLine("Hello World!");
}
}
using static System.Console;

public class Program
{
static void Main()
{
// I did not have to qualify Console
WriteLine("Hello World!");
}
}
you might find it useful
Pobiega
Pobiega8mo ago
very rarely used in C# thou and is a bit controversial when it makes sense, its great thou
__dil__
__dil__8mo ago
Yeah Rider suggested I do that but it felt a bit strange lol But I'll keep it in mind when it makes sense
reflectronic
reflectronic8mo ago
this isn't really something people care about in .NET
Pobiega
Pobiega8mo ago
I think a reason for that is we've had really good editor support since day 1. I really struggled trying to use non C# languages because lack of autocomplete and intellisense just crippled me
reflectronic
reflectronic8mo ago
and i personally find it weird that languages would choose to optimize their import system for preventing naming collisions, which are pretty rare (especially since, in .NET, people do not usually give things generic names)
Pobiega
Pobiega8mo ago
and since we don't have free functions, Add isnt a thing - its something.Add or Something.Add
__dil__
__dil__8mo ago
It's not just about name collision, it also helps you as a dev to understand precisely where items come from and be parsimonious about what's in scope. If we're talking about auto-complete, then having the smallest list of stuff in scope is also beneficial to skim through what's available. less stuff == easier to wrap your head aroundm anyways, I'm not really here to debate things, I'm committed to doing things the C# way 😄 I already learned a ton from you guys, I'm very grateful for you all taking the time to explain things 🙂
reflectronic
reflectronic8mo ago
"namespace pollution" is not really a concern in the .NET world. as an extreme example of this, you can add 'global' imports which automatically apply to every file https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/using-directive#global-modifier and, when you create a new project, a number of global imports are added automatically and libraries are designed around these coarse imports. in .NET it's very rare to just use one or two classes from a namespace. writing a class is easy, so people create lots of them! classes and extension methods which are frequently used together are generally put in the same namespace so that access to them is easy
__dil__
__dil__8mo ago
I see This gives some context to the "why", thanks for explaining 🙂
Accord
Accord8mo ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.