6 Modern C# Features for Cleaner Unity Code

Transform verbose Unity code into elegant, maintainable scripts with modern C# features including switch expressions, pattern matching, and record types.

Why Modern C# Matters for Unity Developers

Unity developers have been writing C# code for years, often falling into familiar patterns that, while functional, can become verbose and harder to maintain over time. Modern C# has introduced features that align perfectly with game development needs--cleaner syntax, reduced boilerplate, and more expressive code that communicates intent clearly.

According to LogRocket's analysis of modern C# features for Unity, these language enhancements help write more concise and readable code in Unity projects. Whether you're building a small indie project or a large-scale game, these features can help you write code that's easier to read, debug, and extend.

This guide explores six modern C# features that every Unity developer should incorporate into their workflow. Our team specializes in helping studios adopt modern development practices that improve code quality across all their projects. From web applications to complex game systems, our experienced developers understand how to leverage modern language features for better results.

What You'll Learn

Six modern C# features that transform your Unity development workflow

Switch Expressions

Replace verbose switch statements with concise expression syntax for cleaner control flow

Pattern Matching

Type-safe conditions that make conditional logic more readable and maintainable

Record Types

Value-based equality and immutability for game data models and configurations

Top-Level Statements

Eliminate boilerplate class declarations for simpler utility scripts

Global Using Directives

Reduce repetitive namespace declarations across all your Unity scripts

Primary Constructors

Streamlined class initialization with C# 12's concise constructor syntax

1. Switch Expressions: Elegant Control Flow

Why Switch Expressions Matter in Unity

Traditional switch statements in C# require significant boilerplate code--each case needs its own block, break statements, and often default handling. Switch expressions, introduced in C# 8, condense this into a single expression that returns a value directly. This is particularly valuable in Unity where you frequently need to handle different character states, item types, or game events.

Before: Traditional Switch Statement
1public class CharacterController : MonoBehaviour2{3 private enum CharacterState { Idle, Running, Jumping, Attacking }4 5 private string GetAnimationClipName(CharacterState state, bool isArmed)6 {7 switch (state)8 {9 case CharacterState.Idle:10 return isArmed ? "Idle_Armed" : "Idle_Unarmed";11 case CharacterState.Running:12 return isArmed ? "Run_Armed" : "Run_Unarmed";13 case CharacterState.Jumping:14 return "Jump";15 case CharacterState.Attacking:16 return isArmed ? "Attack_Weapon" : "Attack_Unarmed";17 default:18 return "Idle_Unarmed";19 }20 }21}
After: Switch Expression
1public class CharacterController : MonoBehaviour2{3 private enum CharacterState { Idle, Running, Jumping, Attacking }4 5 private string GetAnimationClipName(CharacterState state, bool isArmed) => state switch6 {7 CharacterState.Idle => isArmed ? "Idle_Armed" : "Idle_Unarmed",8 CharacterState.Running => isArmed ? "Run_Armed" : "Run_Unarmed",9 CharacterState.Jumping => "Jump",10 CharacterState.Attacking => isArmed ? "Attack_Weapon" : "Attack_Unarmed",11 _ => "Idle_Unarmed"12 };13}

Practical Unity Applications

Switch expressions shine in Unity's event-driven architecture. Consider handling different power-up types in a collection system:

public class PowerUpCollector : MonoBehaviour
{
 public int Collect(PowerUpType type) => type switch
 {
 PowerUpType.Health => AddHealth(25),
 PowerUpType.Shield => AddShield(50),
 PowerUpType.Speed => ApplySpeedBoost(1.5f, 10f),
 PowerUpType.Weapon => UnlockWeapon(),
 _ => 0
 };
}

The pattern also works excellently for UI state management, input handling, and quest state transitions--all common scenarios in Unity projects. Our game development expertise encompasses these modern patterns across various Unity projects. When you're ready to level up your development workflow, our software development team can help you implement these practices across your entire codebase.

2. Pattern Matching: Type-Safe Conditions

The Power of Type Patterns

Pattern matching extends beyond simple value matching to inspect types and properties directly. In Unity, this is invaluable when working with polymorphic game objects, component-based architectures, or event systems that handle various message types.

Before: Traditional Type Checking
1public class InteractionHandler : MonoBehaviour2{3 private void OnTriggerEnter(Collider other)4 {5 var health = other.GetComponent<Health>();6 if (health != null)7 {8 health.Restore(10);9 return;10 }11 12 var shield = other.GetComponent<Shield>();13 if (shield != null)14 {15 shield.Activate();16 return;17 }18 19 var collectible = other.GetComponent<ICollectible>();20 if (collectible != null)21 {22 collectible.Collect();23 return;24 }25 }26}
After: Pattern Matching
1public class InteractionHandler : MonoBehaviour2{3 private void OnTriggerEnter(Collider other)4 {5 switch (other)6 {7 case Health health:8 health.Restore(10);9 break;10 case Shield shield when shield.CurrentCharge < 50:11 shield.Activate();12 break;13 case ICollectible collectible:14 collectible.Collect();15 break;16 }17 }18}

Property Patterns for Game Logic

Property patterns allow matching based on object properties without additional variable declarations:

public class EnemyAI : MonoBehaviour
{
 public void TakeDamage(float damage, DamageSource source)
 {
 if (source is { Type: DamageType.Fire, ElementalDamage: > 50f })
 {
 ApplyBurnEffect();
 }

 if (this.enemy is { Health: < 25, IsVulnerable: true })
 {
 TriggerFleeingBehavior();
 }
 }
}

This pattern is particularly useful in Unity's physics callbacks, event systems, and any scenario where you need to evaluate multiple conditions on objects with known structures.

3. Record Types: Value-Based Data Models

Why Records Excel for Game Data

Record types, introduced in C# 9 and improved in later versions, provide value-based equality comparison and immutability by default. For game development, this is ideal for inventory items, quest data, configuration objects, and any data structure where you want value semantics rather than reference semantics.

Traditional Class vs Record Type
1// Traditional approach2public class ItemData : ScriptableObject3{4 public string itemId;5 public string displayName;6 public int maxStackSize;7 8 public override bool Equals(object obj)9 {10 if (obj is ItemData other)11 return itemId == other.itemId;12 return false;13 }14 15 public override int GetHashCode() => itemId.GetHashCode();16}17 18// Modern record approach19[CreateAssetMenu(fileName = "New Item", menuName = "Inventory/Item")]20public record ItemData(21 string ItemId,22 string DisplayName,23 int MaxStackSize = 9924)25{26 public bool IsStackable => MaxStackSize > 1;27}

4. Top-Level Statements: Eliminating Boilerplate

Reducing File Bloat

Unity projects often contain many small scripts where the class declaration and using statements make up a significant portion of the file. Top-level statements (C# 9) allow you to write code without the traditional class wrapper, keeping small scripts focused and readable.

Top-Level Extension Methods
1// MathExtensions.cs2using UnityEngine;3 4static partial class MathExtensions5{6 public static float Remap(this float value, float from1, float to1, float from2, float to2) =>7 (value - from1) / (to1 - from1) * (to2 - from2) + from2;8 9 public static bool Approximately(this float a, float b, float tolerance = 0.001f) =>10 Mathf.Abs(a - b) < tolerance;11}

When to Use Top-Level Statements

Top-level statements are ideal for:

  • Utility extension methods
  • Simple MonoBehaviour scripts
  • Editor utility scripts
  • One-liner helper functions
  • Shader or material property accessors

They reduce visual noise and make small scripts easier to scan and understand at a glance.

5. Global Using Directives: Namespace Management

Solving the Repetition Problem

Large Unity projects often have many files that all need the same set of using directives--UnityEngine, UnityEngine.AI, System.Collections, and others. Global using directives (C# 10) allow you to declare these once and have them apply to all files in the project.

GlobalUsings.cs
1// GlobalUsings.cs2global using System;3global using System.Collections;4global using System.Collections.Generic;5global using System.Linq;6global using UnityEngine;7global using UnityEngine.AI;8global using UnityEngine.Events;9global using UnityEngine.SceneManagement;

6. Primary Constructors: Streamlined Initialization

C# 12's Latest Contribution

Primary constructors provide a more concise way to declare constructors and initialize properties. For Unity scripts, this can simplify initialization while maintaining compatibility with Unity's component system.

Primary Constructor for MonoBehaviour
1// Traditional approach2public class Enemy : MonoBehaviour3{4 private readonly float maxHealth;5 private float currentHealth;6 private readonly string enemyType;7 8 public Enemy(float maxHealth, string enemyType)9 {10 this.maxHealth = maxHealth;11 this.currentHealth = maxHealth;12 this.enemyType = enemyType;13 }14 15 public float HealthPercent => currentHealth / maxHealth;16}17 18// With primary constructor19public class Enemy(float maxHealth, string enemyType) : MonoBehaviour20{21 public float HealthPercent => currentHealth / maxHealth;22 23 private float currentHealth = maxHealth;24}
Primary Constructor for Data Classes
1public record CharacterClass(2 string Name,3 int BaseHealth,4 int BaseMana,5 List<string> Abilities,6 CharacterStats StartingStats7)8{9 public int EffectiveHealth => BaseHealth + StartingStats.HealthBonus;10 public int EffectiveMana => BaseMana + StartingStats.ManaBonus;11}

This syntax is particularly valuable for data classes and configuration objects, service dependencies, state objects, and command and event structures.

Implementation Checklist

When modernizing your Unity codebase, consider these steps:

  1. Audit Existing Switch Statements - Identify opportunities to convert verbose switch blocks to expressions
  2. Review Type Checking Patterns - Look for repeated is checks and GetComponent calls that could use pattern matching
  3. Identify Data Models - Find classes that would benefit from record semantics
  4. Consolidate Using Statements - Count how many files share common using directives
  5. Evaluate Script Complexity - Identify simple scripts that could use top-level statements
  6. Plan Migration - Start with a single feature type before expanding to all six

Need guidance on implementing these practices? Our software development services can help your team adopt modern coding standards and improve overall code quality across your projects.

Best Practices for Unity Projects

Compatibility Considerations

Unity's scripting runtime version affects which C# features are available:

Runtime VersionC# VersionKey Features Available
.NET Standard 2.1C# 8Switch expressions, pattern matching, readonly members
.NET Framework (Legacy)LimitedLimited modern C# support

Always test new features in a small project before large-scale adoption.

Performance Implications

Modern C# features compile to equivalent IL code--the runtime performance is nearly identical to traditional syntax. The benefits are primarily:

  • Reduced code size
  • Fewer potential bugs
  • Better IDE support (IntelliSense, refactoring)
  • Improved maintainability

Migration Strategy

When updating existing Unity projects:

  1. Add new features incrementally
  2. Use automated refactoring tools in your IDE
  3. Run full test suites after changes
  4. Document feature usage in coding standards
  5. Ensure team members understand new patterns

Conclusion

Modern C# features offer significant improvements for Unity development without sacrificing performance or compatibility. By adopting switch expressions, pattern matching, record types, top-level statements, global using directives, and primary constructors, you can write cleaner, more expressive code that's easier to maintain and extend.

The transition doesn't need to be all at once--start with the features that solve your most pressing code readability issues. As your team becomes comfortable with these patterns, you'll find your Unity projects becoming more maintainable and your development workflow more productive.

Need help modernizing your codebase? Our software development team specializes in helping game studios adopt modern development practices and improve code quality across their projects. From code audits to full refactoring services, we can help you achieve cleaner, more maintainable code.

Frequently Asked Questions

Ready to Modernize Your Unity Development Workflow?

Our team of experienced Unity developers can help you adopt modern C# practices and improve your codebase quality.